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.portlet.documentlibrary.util;
016    
017    import com.liferay.portal.kernel.lar.PortletDataContext;
018    import com.liferay.portal.kernel.log.Log;
019    import com.liferay.portal.kernel.log.LogFactoryUtil;
020    import com.liferay.portal.kernel.messaging.DestinationNames;
021    import com.liferay.portal.kernel.messaging.MessageBusException;
022    import com.liferay.portal.kernel.messaging.MessageBusUtil;
023    import com.liferay.portal.kernel.process.ClassPathUtil;
024    import com.liferay.portal.kernel.process.ProcessCallable;
025    import com.liferay.portal.kernel.process.ProcessException;
026    import com.liferay.portal.kernel.process.ProcessExecutor;
027    import com.liferay.portal.kernel.repository.model.FileEntry;
028    import com.liferay.portal.kernel.repository.model.FileVersion;
029    import com.liferay.portal.kernel.util.FileUtil;
030    import com.liferay.portal.kernel.util.InstancePool;
031    import com.liferay.portal.kernel.util.PropsKeys;
032    import com.liferay.portal.kernel.util.ServerDetector;
033    import com.liferay.portal.kernel.util.SetUtil;
034    import com.liferay.portal.kernel.util.StringBundler;
035    import com.liferay.portal.kernel.util.StringPool;
036    import com.liferay.portal.kernel.util.Validator;
037    import com.liferay.portal.kernel.xml.Element;
038    import com.liferay.portal.log.Log4jLogFactoryImpl;
039    import com.liferay.portal.repository.liferayrepository.model.LiferayFileVersion;
040    import com.liferay.portal.util.PrefsPropsUtil;
041    import com.liferay.portal.util.PropsUtil;
042    import com.liferay.portal.util.PropsValues;
043    import com.liferay.portlet.documentlibrary.NoSuchFileEntryException;
044    import com.liferay.portlet.documentlibrary.store.DLStoreUtil;
045    import com.liferay.util.log4j.Log4JUtil;
046    
047    import java.io.File;
048    import java.io.InputStream;
049    
050    import java.util.List;
051    import java.util.Map;
052    import java.util.Set;
053    import java.util.Vector;
054    
055    import org.apache.commons.lang.time.StopWatch;
056    
057    /**
058     * @author Juan González
059     * @author Sergio González
060     * @author Mika Koivisto
061     */
062    public class AudioProcessorImpl
063            extends DLPreviewableProcessor implements AudioProcessor {
064    
065            public static AudioProcessorImpl getInstance() {
066                    return _instance;
067            }
068    
069            public void exportGeneratedFiles(
070                            PortletDataContext portletDataContext, FileEntry fileEntry,
071                            Element fileEntryElement)
072                    throws Exception {
073    
074                    exportPreview(portletDataContext, fileEntry, fileEntryElement);
075            }
076    
077            public void generateAudio(FileVersion fileVersion) throws Exception {
078                    _instance._generateAudio(fileVersion);
079            }
080    
081            public Set<String> getAudioMimeTypes() {
082                    return _instance._audioMimeTypes;
083            }
084    
085            public InputStream getPreviewAsStream(FileVersion fileVersion)
086                    throws Exception {
087    
088                    return _instance.doGetPreviewAsStream(fileVersion, PREVIEW_TYPE);
089            }
090    
091            public long getPreviewFileSize(FileVersion fileVersion)
092                    throws Exception {
093    
094                    return _instance.doGetPreviewFileSize(fileVersion, PREVIEW_TYPE);
095            }
096    
097            public boolean hasAudio(FileVersion fileVersion) {
098                    boolean hasAudio = false;
099    
100                    try {
101                            hasAudio = _instance._hasAudio(fileVersion);
102    
103                            if (!hasAudio && _instance.isSupported(fileVersion)) {
104                                    _instance._queueGeneration(fileVersion);
105                            }
106                    }
107                    catch (Exception e) {
108                            _log.error(e, e);
109                    }
110    
111                    return hasAudio;
112            }
113    
114            public void importGeneratedFiles(
115                            PortletDataContext portletDataContext, FileEntry fileEntry,
116                            FileEntry importedFileEntry, Element fileEntryElement)
117                    throws Exception {
118    
119                    importPreview(
120                            portletDataContext, fileEntry, importedFileEntry, fileEntryElement,
121                            "audio", PREVIEW_TYPE);
122            }
123    
124            public boolean isAudioSupported(FileVersion fileVersion) {
125                    return _instance.isSupported(fileVersion);
126            }
127    
128            public boolean isAudioSupported(String mimeType) {
129                    return _instance.isSupported(mimeType);
130            }
131    
132            public boolean isSupported(String mimeType) {
133                    if (Validator.isNull(mimeType)) {
134                            return false;
135                    }
136    
137                    try {
138                            if (PrefsPropsUtil.getBoolean(
139                                            PropsKeys.XUGGLER_ENABLED, PropsValues.XUGGLER_ENABLED)) {
140    
141                                    return _audioMimeTypes.contains(mimeType);
142                            }
143                    }
144                    catch (Exception e) {
145                    }
146    
147                    return false;
148            }
149    
150            public void trigger(FileVersion fileVersion) {
151                    _instance._queueGeneration(fileVersion);
152            }
153    
154            protected void exportPreview(
155                            PortletDataContext portletDataContext, FileEntry fileEntry,
156                            Element fileEntryElement)
157                    throws Exception {
158    
159                    FileVersion fileVersion = fileEntry.getFileVersion();
160    
161                    if (!isSupported(fileVersion)) {
162                            return;
163                    }
164    
165                    exportPreview(
166                            portletDataContext, fileEntry, fileEntryElement, "audio",
167                            PREVIEW_TYPE);
168            }
169    
170            @Override
171            protected String getPreviewType(FileVersion fileVersion) {
172                    return PREVIEW_TYPE;
173            }
174    
175            @Override
176            protected String getThumbnailType(FileVersion fileVersion) {
177                    return null;
178            }
179    
180            private AudioProcessorImpl() {
181                    FileUtil.mkdirs(PREVIEW_TMP_PATH);
182            }
183    
184            private void _generateAudio(FileVersion fileVersion) throws Exception {
185                    String tempFileId = DLUtil.getTempFileId(
186                            fileVersion.getFileEntryId(), fileVersion.getVersion());
187    
188                    File audioTempFile = _getAudioTempFile(
189                            tempFileId, fileVersion.getExtension());
190                    File previewTempFile = getPreviewTempFile(tempFileId);
191    
192                    try {
193                            if (!PrefsPropsUtil.getBoolean(
194                                            PropsKeys.XUGGLER_ENABLED, PropsValues.XUGGLER_ENABLED) ||
195                                    _hasAudio(fileVersion)) {
196    
197                                    return;
198                            }
199    
200                            if (_isGeneratePreview(fileVersion)) {
201                                    File file = null;
202    
203                                    if (fileVersion instanceof LiferayFileVersion) {
204                                            try {
205                                                    LiferayFileVersion liferayFileVersion =
206                                                            (LiferayFileVersion)fileVersion;
207    
208                                                    file = liferayFileVersion.getFile(false);
209                                            }
210                                            catch (UnsupportedOperationException uoe) {
211                                            }
212                                    }
213    
214                                    if (file == null) {
215                                            InputStream inputStream = fileVersion.getContentStream(
216                                                    false);
217    
218                                            FileUtil.write(audioTempFile, inputStream);
219    
220                                            file = audioTempFile;
221                                    }
222    
223                                    try {
224                                            _generateAudioXuggler(fileVersion, file, previewTempFile);
225                                    }
226                                    catch (Exception e) {
227                                            _log.error(e, e);
228                                    }
229                            }
230                    }
231                    catch (NoSuchFileEntryException nsfee) {
232                    }
233                    finally {
234                            _fileVersionIds.remove(fileVersion.getFileVersionId());
235    
236                            FileUtil.delete(audioTempFile);
237                            FileUtil.delete(previewTempFile);
238                    }
239            }
240    
241            private void _generateAudioXuggler(
242                            FileVersion fileVersion, File srcFile, File destFile)
243                    throws Exception {
244    
245                    StopWatch stopWatch = null;
246    
247                    if (_log.isInfoEnabled()) {
248                            stopWatch = new StopWatch();
249    
250                            stopWatch.start();
251                    }
252    
253                    try {
254                            if (PropsValues.DL_FILE_ENTRY_PREVIEW_FORK_PROCESS_ENABLED) {
255                                    ProcessCallable<String> processCallable =
256                                            new LiferayAudioProcessCallable(
257                                                    ServerDetector.getServerId(),
258                                                    PropsUtil.get(PropsKeys.LIFERAY_HOME),
259                                                    Log4JUtil.getCustomLogSettings(),
260                                                    srcFile.getCanonicalPath(),
261                                                    destFile.getCanonicalPath());
262    
263                                    ProcessExecutor.execute(
264                                            processCallable, ClassPathUtil.getPortalClassPath());
265                            }
266                            else {
267                                    LiferayConverter liferayConverter = new LiferayAudioConverter(
268                                            srcFile.getCanonicalPath(), destFile.getCanonicalPath());
269    
270                                    liferayConverter.convert();
271                            }
272                    }
273                    catch (Exception e) {
274                            _log.error(e, e);
275                    }
276    
277                    addFileToStore(
278                            fileVersion.getCompanyId(), PREVIEW_PATH,
279                            getPreviewFilePath(fileVersion), destFile);
280    
281                    if (_log.isInfoEnabled()) {
282                            _log.info(
283                                    "Xuggler generated a preview audio for " +
284                                            fileVersion.getTitle() + " in " + stopWatch);
285                    }
286            }
287    
288            private File _getAudioTempFile(String tempFileId, String targetExtension) {
289                    String audioTempFilePath = _getAudioTempFilePath(
290                            tempFileId, targetExtension);
291    
292                    return new File(audioTempFilePath);
293            }
294    
295            private String _getAudioTempFilePath(
296                    String tempFileId, String targetExtension) {
297    
298                    StringBundler sb = new StringBundler(5);
299    
300                    sb.append(PREVIEW_TMP_PATH);
301                    sb.append(tempFileId);
302    
303                    if (PREVIEW_TYPE.equals(targetExtension)) {
304                            sb.append("_tmp");
305                    }
306    
307                    sb.append(StringPool.PERIOD);
308                    sb.append(targetExtension);
309    
310                    return sb.toString();
311            }
312    
313            private boolean _hasAudio(FileVersion fileVersion) throws Exception {
314                    if (!isSupported(fileVersion)) {
315                            return false;
316                    }
317    
318                    boolean previewExists = DLStoreUtil.hasFile(
319                            fileVersion.getCompanyId(), REPOSITORY_ID,
320                            getPreviewFilePath(fileVersion));
321    
322                    if (PropsValues.DL_FILE_ENTRY_PREVIEW_ENABLED && previewExists) {
323                            return true;
324                    }
325    
326                    return false;
327            }
328    
329            private boolean _isGeneratePreview(FileVersion fileVersion)
330                    throws Exception {
331    
332                    String previewFilePath = getPreviewFilePath(fileVersion);
333    
334                    if (PropsValues.DL_FILE_ENTRY_PREVIEW_ENABLED &&
335                            !DLStoreUtil.hasFile(
336                                    fileVersion.getCompanyId(), REPOSITORY_ID, previewFilePath)) {
337    
338                            return true;
339                    }
340                    else {
341                            return false;
342                    }
343            }
344    
345            private void _queueGeneration(FileVersion fileVersion) {
346                    if (_fileVersionIds.contains(fileVersion.getFileVersionId()) ||
347                            !isSupported(fileVersion)) {
348    
349                            return;
350                    }
351    
352                    _fileVersionIds.add(fileVersion.getFileVersionId());
353    
354                    if (PropsValues.DL_FILE_ENTRY_PROCESSORS_TRIGGER_SYNCHRONOUSLY) {
355                            try {
356                                    MessageBusUtil.sendSynchronousMessage(
357                                            DestinationNames.DOCUMENT_LIBRARY_AUDIO_PROCESSOR,
358                                            fileVersion);
359                            }
360                            catch (MessageBusException mbe) {
361                                    if (_log.isWarnEnabled()) {
362                                            _log.warn(mbe, mbe);
363                                    }
364                            }
365                    }
366                    else {
367                            MessageBusUtil.sendMessage(
368                                    DestinationNames.DOCUMENT_LIBRARY_AUDIO_PROCESSOR, fileVersion);
369                    }
370            }
371    
372            private static Log _log = LogFactoryUtil.getLog(AudioProcessorImpl.class);
373    
374            private static AudioProcessorImpl _instance = new AudioProcessorImpl();
375    
376            static {
377                    InstancePool.put(AudioProcessorImpl.class.getName(), _instance);
378            }
379    
380            private Set<String> _audioMimeTypes = SetUtil.fromArray(
381                    PropsValues.DL_FILE_ENTRY_PREVIEW_AUDIO_MIME_TYPES);
382            private List<Long> _fileVersionIds = new Vector<Long>();
383    
384            private static class LiferayAudioProcessCallable
385                    implements ProcessCallable<String> {
386    
387                    public LiferayAudioProcessCallable(
388                            String serverId, String liferayHome,
389                            Map<String, String> customLogSettings, String inputURL,
390                            String outputURL) {
391    
392                            _serverId = serverId;
393                            _liferayHome = liferayHome;
394                            _customLogSettings = customLogSettings;
395                            _inputURL = inputURL;
396                            _outputURL = outputURL;
397                    }
398    
399                    public String call() throws ProcessException {
400                            Class<?> clazz = getClass();
401    
402                            ClassLoader classLoader = clazz.getClassLoader();
403    
404                            Log4JUtil.initLog4J(
405                                    _serverId, _liferayHome, classLoader, new Log4jLogFactoryImpl(),
406                                    _customLogSettings);
407    
408                            try {
409                                    LiferayConverter liferayConverter = new LiferayAudioConverter(
410                                            _inputURL, _outputURL);
411    
412                                    liferayConverter.convert();
413                            }
414                            catch (Exception e) {
415                                    throw new ProcessException(e);
416                            }
417    
418                            return StringPool.BLANK;
419                    }
420    
421                    private Map<String, String> _customLogSettings;
422                    private String _inputURL;
423                    private String _liferayHome;
424                    private String _outputURL;
425                    private String _serverId;
426    
427            }
428    
429    }