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