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