001    /**
002     * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.image;
016    
017    import com.liferay.portal.kernel.configuration.Filter;
018    import com.liferay.portal.kernel.image.ImageMagick;
019    import com.liferay.portal.kernel.log.Log;
020    import com.liferay.portal.kernel.log.LogFactoryUtil;
021    import com.liferay.portal.kernel.util.NamedThreadFactory;
022    import com.liferay.portal.kernel.util.OSDetector;
023    import com.liferay.portal.kernel.util.PropsKeys;
024    import com.liferay.portal.kernel.util.StringBundler;
025    import com.liferay.portal.kernel.util.Validator;
026    import com.liferay.portal.util.ClassLoaderUtil;
027    import com.liferay.portal.util.PrefsPropsUtil;
028    import com.liferay.portal.util.PropsUtil;
029    
030    import java.util.LinkedList;
031    import java.util.List;
032    import java.util.Properties;
033    import java.util.concurrent.Future;
034    
035    import javax.portlet.PortletPreferences;
036    
037    import org.im4java.process.ArrayListOutputConsumer;
038    import org.im4java.process.ProcessExecutor;
039    import org.im4java.process.ProcessTask;
040    
041    /**
042     * @author Alexander Chow
043     * @author Ivica Cardic
044     */
045    public class ImageMagickImpl implements ImageMagick {
046    
047            public static ImageMagickImpl getInstance() {
048                    return _instance;
049            }
050    
051            public Future<?> convert(List<String> arguments) throws Exception {
052                    if (!isEnabled()) {
053                            throw new IllegalStateException(
054                                    "Cannot call \"convert\" when ImageMagick is disabled");
055                    }
056    
057                    ProcessExecutor processExecutor = _getProcessExecutor();
058    
059                    LiferayConvertCmd liferayConvertCmd = new LiferayConvertCmd();
060    
061                    ProcessTask processTask = liferayConvertCmd.getProcessTask(
062                            _globalSearchPath, getResourceLimits(), arguments);
063    
064                    processExecutor.execute(processTask);
065    
066                    return processTask;
067            }
068    
069            public void destroy() {
070                    if (_processExecutor == null) {
071                            return;
072                    }
073    
074                    synchronized (ProcessExecutor.class) {
075                            _processExecutor.shutdownNow();
076                    }
077    
078                    _processExecutor = null;
079            }
080    
081            public String getGlobalSearchPath() throws Exception {
082                    PortletPreferences preferences = PrefsPropsUtil.getPreferences();
083    
084                    String globalSearchPath = preferences.getValue(
085                            PropsKeys.IMAGEMAGICK_GLOBAL_SEARCH_PATH, null);
086    
087                    if (Validator.isNotNull(globalSearchPath)) {
088                            return globalSearchPath;
089                    }
090    
091                    String filterName = null;
092    
093                    if (OSDetector.isApple()) {
094                            filterName = "apple";
095                    }
096                    else if (OSDetector.isWindows()) {
097                            filterName = "windows";
098                    }
099                    else {
100                            filterName = "unix";
101                    }
102    
103                    return PropsUtil.get(
104                            PropsKeys.IMAGEMAGICK_GLOBAL_SEARCH_PATH, new Filter(filterName));
105            }
106    
107            public Properties getResourceLimitsProperties() throws Exception {
108                    Properties resourceLimitsProperties = PrefsPropsUtil.getProperties(
109                            PropsKeys.IMAGEMAGICK_RESOURCE_LIMIT, true);
110    
111                    if (resourceLimitsProperties.isEmpty()) {
112                            resourceLimitsProperties = PropsUtil.getProperties(
113                                    PropsKeys.IMAGEMAGICK_RESOURCE_LIMIT, true);
114                    }
115    
116                    return resourceLimitsProperties;
117            }
118    
119            public String[] identify(List<String> arguments) throws Exception {
120                    if (!isEnabled()) {
121                            throw new IllegalStateException(
122                                    "Cannot call \"identify\" when ImageMagick is disabled");
123                    }
124    
125                    ProcessExecutor processExecutor = _getProcessExecutor();
126    
127                    LiferayIdentifyCmd liferayIdentifyCmd = new LiferayIdentifyCmd();
128    
129                    ArrayListOutputConsumer arrayListOutputConsumer =
130                            new ArrayListOutputConsumer();
131    
132                    liferayIdentifyCmd.setOutputConsumer(arrayListOutputConsumer);
133    
134                    ProcessTask processTask = liferayIdentifyCmd.getProcessTask(
135                            _globalSearchPath, getResourceLimits(), arguments);
136    
137                    processExecutor.execute(processTask);
138    
139                    processTask.get();
140    
141                    List<String> output = arrayListOutputConsumer.getOutput();
142    
143                    if (output != null) {
144                            return output.toArray(new String[output.size()]);
145                    }
146    
147                    return new String[0];
148            }
149    
150            public boolean isEnabled() {
151                    boolean enabled = false;
152    
153                    try {
154                            enabled = PrefsPropsUtil.getBoolean(PropsKeys.IMAGEMAGICK_ENABLED);
155                    }
156                    catch (Exception e) {
157                            _log.warn(e, e);
158                    }
159    
160                    if (!enabled && !_warned) {
161                            StringBundler sb = new StringBundler(7);
162    
163                            sb.append("Liferay is not configured to use ImageMagick and ");
164                            sb.append("Ghostscript. For better quality document and image ");
165                            sb.append("previews, install ImageMagick and Ghostscript. Enable ");
166                            sb.append("ImageMagick in portal-ext.properties or in the Server ");
167                            sb.append("Administration section of the Control Panel at: ");
168                            sb.append("http://<server>/group/control_panel/manage/-/server/");
169                            sb.append("external-services");
170    
171                            _log.warn(sb.toString());
172    
173                            _warned = true;
174                    }
175    
176                    return enabled;
177            }
178    
179            public void reset() {
180                    if (isEnabled()) {
181                            try {
182                                    _globalSearchPath = getGlobalSearchPath();
183    
184                                    _resourceLimitsProperties = getResourceLimitsProperties();
185                            }
186                            catch (Exception e) {
187                                    _log.error(e, e);
188                            }
189                    }
190            }
191    
192            protected LinkedList<String> getResourceLimits() {
193                    LinkedList<String> resourceLimits = new LinkedList<String>();
194    
195                    if (_resourceLimitsProperties == null) {
196                            return resourceLimits;
197                    }
198    
199                    for (Object key : _resourceLimitsProperties.keySet()) {
200                            String value = (String)_resourceLimitsProperties.get(key);
201    
202                            if (Validator.isNull(value)) {
203                                    continue;
204                            }
205    
206                            resourceLimits.add("-limit");
207                            resourceLimits.add((String)key);
208                            resourceLimits.add(value);
209                    }
210    
211                    return resourceLimits;
212            }
213    
214            private ProcessExecutor _getProcessExecutor() {
215                    if (_processExecutor != null) {
216                            return _processExecutor;
217                    }
218    
219                    synchronized (ProcessExecutor.class) {
220                            if (_processExecutor == null) {
221                                    _processExecutor = new ProcessExecutor();
222    
223                                    _processExecutor.setThreadFactory(
224                                            new NamedThreadFactory(
225                                                    ImageMagickImpl.class.getName(), Thread.MIN_PRIORITY,
226                                                    ClassLoaderUtil.getPortalClassLoader()));
227                            }
228                    }
229    
230                    return _processExecutor;
231            }
232    
233            private static Log _log = LogFactoryUtil.getLog(ImageMagickImpl.class);
234    
235            private static ImageMagickImpl _instance = new ImageMagickImpl();
236    
237            private String _globalSearchPath;
238            private volatile ProcessExecutor _processExecutor;
239            private Properties _resourceLimitsProperties;
240            private boolean _warned;
241    
242    }