001    /**
002     * Copyright (c) 2000-present 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.security.pacl.DoPrivileged;
022    import com.liferay.portal.kernel.util.ClassLoaderUtil;
023    import com.liferay.portal.kernel.util.NamedThreadFactory;
024    import com.liferay.portal.kernel.util.OSDetector;
025    import com.liferay.portal.kernel.util.PropsKeys;
026    import com.liferay.portal.kernel.util.StringBundler;
027    import com.liferay.portal.kernel.util.Validator;
028    import com.liferay.portal.util.PrefsPropsUtil;
029    import com.liferay.portal.util.PropsUtil;
030    
031    import java.util.LinkedList;
032    import java.util.List;
033    import java.util.Map;
034    import java.util.Properties;
035    import java.util.concurrent.Future;
036    
037    import javax.portlet.PortletPreferences;
038    
039    import org.im4java.process.ArrayListOutputConsumer;
040    import org.im4java.process.ProcessExecutor;
041    import org.im4java.process.ProcessTask;
042    
043    /**
044     * @author Alexander Chow
045     * @author Ivica Cardic
046     */
047    @DoPrivileged
048    public class ImageMagickImpl implements ImageMagick {
049    
050            public static ImageMagickImpl getInstance() {
051                    return _instance;
052            }
053    
054            @Override
055            public Future<?> convert(List<String> arguments) throws Exception {
056                    if (!isEnabled()) {
057                            throw new IllegalStateException(
058                                    "Cannot call \"convert\" when ImageMagick is disabled");
059                    }
060    
061                    ProcessExecutor processExecutor = _getProcessExecutor();
062    
063                    LiferayConvertCmd liferayConvertCmd = new LiferayConvertCmd();
064    
065                    ProcessTask processTask = liferayConvertCmd.getProcessTask(
066                            _globalSearchPath, getResourceLimits(), arguments);
067    
068                    processExecutor.execute(processTask);
069    
070                    return processTask;
071            }
072    
073            @Override
074            public void destroy() {
075                    if (_processExecutor == null) {
076                            return;
077                    }
078    
079                    synchronized (ProcessExecutor.class) {
080                            _processExecutor.shutdownNow();
081                    }
082    
083                    _processExecutor = null;
084            }
085    
086            @Override
087            public String getGlobalSearchPath() throws Exception {
088                    PortletPreferences preferences = PrefsPropsUtil.getPreferences(true);
089    
090                    String globalSearchPath = preferences.getValue(
091                            PropsKeys.IMAGEMAGICK_GLOBAL_SEARCH_PATH, null);
092    
093                    if (Validator.isNotNull(globalSearchPath)) {
094                            return globalSearchPath;
095                    }
096    
097                    String filterName = null;
098    
099                    if (OSDetector.isApple()) {
100                            filterName = "apple";
101                    }
102                    else if (OSDetector.isWindows()) {
103                            filterName = "windows";
104                    }
105                    else {
106                            filterName = "unix";
107                    }
108    
109                    return PropsUtil.get(
110                            PropsKeys.IMAGEMAGICK_GLOBAL_SEARCH_PATH, new Filter(filterName));
111            }
112    
113            @Override
114            public Properties getResourceLimitsProperties() throws Exception {
115                    Properties resourceLimitsProperties = PrefsPropsUtil.getProperties(
116                            PropsKeys.IMAGEMAGICK_RESOURCE_LIMIT, true);
117    
118                    if (resourceLimitsProperties.isEmpty()) {
119                            resourceLimitsProperties = PropsUtil.getProperties(
120                                    PropsKeys.IMAGEMAGICK_RESOURCE_LIMIT, true);
121                    }
122    
123                    return resourceLimitsProperties;
124            }
125    
126            @Override
127            public String[] identify(List<String> arguments) throws Exception {
128                    if (!isEnabled()) {
129                            throw new IllegalStateException(
130                                    "Cannot call \"identify\" when ImageMagick is disabled");
131                    }
132    
133                    ProcessExecutor processExecutor = _getProcessExecutor();
134    
135                    LiferayIdentifyCmd liferayIdentifyCmd = new LiferayIdentifyCmd();
136    
137                    ArrayListOutputConsumer arrayListOutputConsumer =
138                            new ArrayListOutputConsumer();
139    
140                    liferayIdentifyCmd.setOutputConsumer(arrayListOutputConsumer);
141    
142                    ProcessTask processTask = liferayIdentifyCmd.getProcessTask(
143                            _globalSearchPath, getResourceLimits(), arguments);
144    
145                    processExecutor.execute(processTask);
146    
147                    processTask.get();
148    
149                    List<String> output = arrayListOutputConsumer.getOutput();
150    
151                    if (output != null) {
152                            return output.toArray(new String[output.size()]);
153                    }
154    
155                    return new String[0];
156            }
157    
158            @Override
159            public boolean isEnabled() {
160                    boolean enabled = false;
161    
162                    try {
163                            enabled = PrefsPropsUtil.getBoolean(PropsKeys.IMAGEMAGICK_ENABLED);
164                    }
165                    catch (Exception e) {
166                            if (_log.isWarnEnabled()) {
167                                    _log.warn(e, e);
168                            }
169                    }
170    
171                    if (!enabled && !_warned && _log.isWarnEnabled()) {
172                            StringBundler sb = new StringBundler(7);
173    
174                            sb.append("Liferay is not configured to use ImageMagick and ");
175                            sb.append("Ghostscript. For better quality document and image ");
176                            sb.append("previews, install ImageMagick and Ghostscript. Enable ");
177                            sb.append("ImageMagick in portal-ext.properties or in the Server ");
178                            sb.append("Administration section of the Control Panel at: ");
179                            sb.append("http://<server>/group/control_panel/manage/-/server/");
180                            sb.append("external-services");
181    
182                            _log.warn(sb.toString());
183    
184                            _warned = true;
185                    }
186    
187                    return enabled;
188            }
189    
190            @Override
191            public void reset() {
192                    if (isEnabled()) {
193                            try {
194                                    _globalSearchPath = getGlobalSearchPath();
195    
196                                    _resourceLimitsProperties = getResourceLimitsProperties();
197                            }
198                            catch (Exception e) {
199                                    _log.error(e, e);
200                            }
201                    }
202            }
203    
204            protected LinkedList<String> getResourceLimits() {
205                    LinkedList<String> resourceLimits = new LinkedList<>();
206    
207                    if (_resourceLimitsProperties == null) {
208                            return resourceLimits;
209                    }
210    
211                    for (Map.Entry<Object, Object> entry :
212                                    _resourceLimitsProperties.entrySet()) {
213    
214                            String value = (String)entry.getValue();
215    
216                            if (Validator.isNull(value)) {
217                                    continue;
218                            }
219    
220                            resourceLimits.add("-limit");
221                            resourceLimits.add((String)entry.getKey());
222                            resourceLimits.add(value);
223                    }
224    
225                    return resourceLimits;
226            }
227    
228            private ProcessExecutor _getProcessExecutor() {
229                    if (_processExecutor != null) {
230                            return _processExecutor;
231                    }
232    
233                    synchronized (ProcessExecutor.class) {
234                            if (_processExecutor == null) {
235                                    _processExecutor = new ProcessExecutor();
236    
237                                    _processExecutor.setThreadFactory(
238                                            new NamedThreadFactory(
239                                                    ImageMagickImpl.class.getName(), Thread.MIN_PRIORITY,
240                                                    ClassLoaderUtil.getPortalClassLoader()));
241                            }
242                    }
243    
244                    return _processExecutor;
245            }
246    
247            private static final Log _log = LogFactoryUtil.getLog(
248                    ImageMagickImpl.class);
249    
250            private static final ImageMagickImpl _instance = new ImageMagickImpl();
251    
252            private String _globalSearchPath;
253            private volatile ProcessExecutor _processExecutor;
254            private Properties _resourceLimitsProperties;
255            private boolean _warned;
256    
257    }