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.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.PropsKeys;
031    import com.liferay.portal.kernel.util.ServerDetector;
032    import com.liferay.portal.kernel.util.SetUtil;
033    import com.liferay.portal.kernel.util.StreamUtil;
034    import com.liferay.portal.kernel.util.StringBundler;
035    import com.liferay.portal.kernel.util.StringPool;
036    import com.liferay.portal.kernel.util.SystemEnv;
037    import com.liferay.portal.kernel.util.Validator;
038    import com.liferay.portal.kernel.xml.Element;
039    import com.liferay.portal.kernel.xuggler.XugglerUtil;
040    import com.liferay.portal.log.Log4jLogFactoryImpl;
041    import com.liferay.portal.repository.liferayrepository.model.LiferayFileVersion;
042    import com.liferay.portal.util.PropsUtil;
043    import com.liferay.portal.util.PropsValues;
044    import com.liferay.portlet.documentlibrary.NoSuchFileEntryException;
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.Properties;
053    import java.util.Set;
054    import java.util.Vector;
055    
056    import org.apache.commons.lang.time.StopWatch;
057    
058    /**
059     * @author Juan Gonz??lez
060     * @author Sergio Gonz??lez
061     * @author Mika Koivisto
062     */
063    public class AudioProcessorImpl
064            extends DLPreviewableProcessor implements AudioProcessor {
065    
066            @Override
067            public void afterPropertiesSet() {
068                    boolean valid = true;
069    
070                    if ((_PREVIEW_TYPES.length == 0) || (_PREVIEW_TYPES.length > 2)) {
071                            valid = false;
072                    }
073                    else {
074                            for (String previewType : _PREVIEW_TYPES) {
075                                    if (!previewType.equals("mp3") && !previewType.equals("ogg")) {
076                                            valid = false;
077    
078                                            break;
079                                    }
080                            }
081                    }
082    
083                    if (!valid && _log.isWarnEnabled()) {
084                            StringBundler sb = new StringBundler(5);
085    
086                            sb.append("Liferay is incorrectly configured to generate video ");
087                            sb.append("previews using video containers other than MP3 or ");
088                            sb.append("OGG. Please change the property ");
089                            sb.append(PropsKeys.DL_FILE_ENTRY_PREVIEW_AUDIO_CONTAINERS);
090                            sb.append(" in portal-ext.properties.");
091    
092                            _log.warn(sb.toString());
093                    }
094    
095                    FileUtil.mkdirs(PREVIEW_TMP_PATH);
096            }
097    
098            @Override
099            public void generateAudio(FileVersion fileVersion) throws Exception {
100                    _generateAudio(fileVersion);
101            }
102    
103            @Override
104            public Set<String> getAudioMimeTypes() {
105                    return _audioMimeTypes;
106            }
107    
108            @Override
109            public InputStream getPreviewAsStream(FileVersion fileVersion, String type)
110                    throws Exception {
111    
112                    return doGetPreviewAsStream(fileVersion, type);
113            }
114    
115            @Override
116            public long getPreviewFileSize(FileVersion fileVersion, String type)
117                    throws Exception {
118    
119                    return doGetPreviewFileSize(fileVersion, type);
120            }
121    
122            @Override
123            public boolean hasAudio(FileVersion fileVersion) {
124                    boolean hasAudio = false;
125    
126                    try {
127                            hasAudio = _hasAudio(fileVersion);
128    
129                            if (!hasAudio && isSupported(fileVersion)) {
130                                    _queueGeneration(fileVersion);
131                            }
132                    }
133                    catch (Exception e) {
134                            _log.error(e, e);
135                    }
136    
137                    return hasAudio;
138            }
139    
140            @Override
141            public boolean isAudioSupported(FileVersion fileVersion) {
142                    return isSupported(fileVersion);
143            }
144    
145            @Override
146            public boolean isAudioSupported(String mimeType) {
147                    return isSupported(mimeType);
148            }
149    
150            @Override
151            public boolean isSupported(String mimeType) {
152                    if (Validator.isNull(mimeType)) {
153                            return false;
154                    }
155    
156                    try {
157                            if (XugglerUtil.isEnabled()) {
158                                    return _audioMimeTypes.contains(mimeType);
159                            }
160                    }
161                    catch (Exception e) {
162                    }
163    
164                    return false;
165            }
166    
167            @Override
168            public void trigger(FileVersion fileVersion) {
169                    _queueGeneration(fileVersion);
170            }
171    
172            @Override
173            protected void doExportGeneratedFiles(
174                            PortletDataContext portletDataContext, FileEntry fileEntry,
175                            Element fileEntryElement)
176                    throws Exception {
177    
178                    exportPreviews(portletDataContext, fileEntry, fileEntryElement);
179            }
180    
181            @Override
182            protected void doImportGeneratedFiles(
183                            PortletDataContext portletDataContext, FileEntry fileEntry,
184                            FileEntry importedFileEntry, Element fileEntryElement)
185                    throws Exception {
186    
187                    importPreviews(
188                            portletDataContext, fileEntry, importedFileEntry, fileEntryElement);
189            }
190    
191            protected void exportPreviews(
192                            PortletDataContext portletDataContext, FileEntry fileEntry,
193                            Element fileEntryElement)
194                    throws Exception {
195    
196                    FileVersion fileVersion = fileEntry.getFileVersion();
197    
198                    if (!isSupported(fileVersion) || !hasPreviews(fileVersion)) {
199                            return;
200                    }
201    
202                    if (!portletDataContext.isPerformDirectBinaryImport()) {
203                            if ((_PREVIEW_TYPES.length == 0) || (_PREVIEW_TYPES.length > 2)) {
204                                    return;
205                            }
206    
207                            for (String previewType : _PREVIEW_TYPES) {
208                                    if (previewType.equals("mp3") || previewType.equals("ogg")) {
209                                            exportPreview(
210                                                    portletDataContext, fileEntry, fileEntryElement,
211                                                    "audio", previewType);
212                                    }
213                            }
214                    }
215            }
216    
217            @Override
218            protected String getPreviewType(FileVersion fileVersion) {
219                    return _PREVIEW_TYPES[0];
220            }
221    
222            @Override
223            protected String[] getPreviewTypes() {
224                    return _PREVIEW_TYPES;
225            }
226    
227            @Override
228            protected String getThumbnailType(FileVersion fileVersion) {
229                    return null;
230            }
231    
232            protected void importPreviews(
233                            PortletDataContext portletDataContext, FileEntry fileEntry,
234                            FileEntry importedFileEntry, Element fileEntryElement)
235                    throws Exception {
236    
237                    if ((_PREVIEW_TYPES.length == 0) || (_PREVIEW_TYPES.length > 2)) {
238                            return;
239                    }
240    
241                    for (String previewType : _PREVIEW_TYPES) {
242                            if (previewType.equals("mp3") || previewType.equals("ogg")) {
243                                    importPreview(
244                                            portletDataContext, fileEntry, importedFileEntry,
245                                            fileEntryElement, "audio", previewType);
246                            }
247                    }
248            }
249    
250            private void _generateAudio(FileVersion fileVersion) throws Exception {
251                    String tempFileId = DLUtil.getTempFileId(
252                            fileVersion.getFileEntryId(), fileVersion.getVersion());
253    
254                    File[] previewTempFiles = new File[_PREVIEW_TYPES.length];
255    
256                    for (int i = 0; i < _PREVIEW_TYPES.length; i++) {
257                            previewTempFiles[i] = getPreviewTempFile(
258                                    tempFileId, _PREVIEW_TYPES[i]);
259                    }
260    
261                    File audioTempFile = null;
262    
263                    InputStream inputStream = null;
264    
265                    try {
266                            audioTempFile = FileUtil.createTempFile(fileVersion.getExtension());
267    
268                            if (!XugglerUtil.isEnabled() || _hasAudio(fileVersion)) {
269                                    return;
270                            }
271    
272                            if (!hasPreviews(fileVersion)) {
273                                    File file = null;
274    
275                                    if (fileVersion instanceof LiferayFileVersion) {
276                                            try {
277                                                    LiferayFileVersion liferayFileVersion =
278                                                            (LiferayFileVersion)fileVersion;
279    
280                                                    file = liferayFileVersion.getFile(false);
281                                            }
282                                            catch (UnsupportedOperationException uoe) {
283                                            }
284                                    }
285    
286                                    if (file == null) {
287                                            inputStream = fileVersion.getContentStream(false);
288    
289                                            FileUtil.write(audioTempFile, inputStream);
290    
291                                            file = audioTempFile;
292                                    }
293    
294                                    try {
295                                            _generateAudioXuggler(fileVersion, file, previewTempFiles);
296                                    }
297                                    catch (Exception e) {
298                                            _log.error(e, e);
299                                    }
300                            }
301                    }
302                    catch (NoSuchFileEntryException nsfee) {
303                    }
304                    finally {
305                            StreamUtil.cleanUp(inputStream);
306    
307                            _fileVersionIds.remove(fileVersion.getFileVersionId());
308    
309                            for (int i = 0; i < previewTempFiles.length; i++) {
310                                    FileUtil.delete(previewTempFiles[i]);
311                            }
312    
313                            FileUtil.delete(audioTempFile);
314                    }
315            }
316    
317            private void _generateAudioXuggler(
318                            FileVersion fileVersion, File srcFile, File destFile,
319                            String containerType)
320                    throws Exception {
321    
322                    if (hasPreview(fileVersion, containerType)) {
323                            return;
324                    }
325    
326                    StopWatch stopWatch = null;
327    
328                    if (_log.isInfoEnabled()) {
329                            stopWatch = new StopWatch();
330    
331                            stopWatch.start();
332                    }
333    
334                    try {
335                            if (PropsValues.DL_FILE_ENTRY_PREVIEW_FORK_PROCESS_ENABLED) {
336                                    ProcessCallable<String> processCallable =
337                                            new LiferayAudioProcessCallable(
338                                                    ServerDetector.getServerId(),
339                                                    PropsUtil.get(PropsKeys.LIFERAY_HOME),
340                                                    Log4JUtil.getCustomLogSettings(),
341                                                    srcFile.getCanonicalPath(), destFile.getCanonicalPath(),
342                                                    containerType,
343                                                    PropsUtil.getProperties(
344                                                            PropsKeys.DL_FILE_ENTRY_PREVIEW_AUDIO, false));
345    
346                                    ProcessExecutor.execute(
347                                            processCallable, ClassPathUtil.getPortalClassPath());
348                            }
349                            else {
350                                    LiferayConverter liferayConverter = new LiferayAudioConverter(
351                                            srcFile.getCanonicalPath(), destFile.getCanonicalPath(),
352                                            containerType,
353                                            PropsUtil.getProperties(
354                                                    PropsKeys.DL_FILE_ENTRY_PREVIEW_AUDIO, false));
355    
356                                    liferayConverter.convert();
357                            }
358                    }
359                    catch (Exception e) {
360                            _log.error(e, e);
361                    }
362    
363                    addFileToStore(
364                            fileVersion.getCompanyId(), PREVIEW_PATH,
365                            getPreviewFilePath(fileVersion, containerType), destFile);
366    
367                    if (_log.isInfoEnabled()) {
368                            _log.info(
369                                    "Xuggler generated a " + containerType + " preview audio for " +
370                                            fileVersion.getFileVersionId() + " in " +
371                                                    stopWatch.getTime() + "ms");
372                    }
373            }
374    
375            private void _generateAudioXuggler(
376                    FileVersion fileVersion, File srcFile, File[] destFiles) {
377    
378                    try {
379                            for (int i = 0; i < destFiles.length; i++) {
380                                    _generateAudioXuggler(
381                                            fileVersion, srcFile, destFiles[i], _PREVIEW_TYPES[i]);
382                            }
383                    }
384                    catch (Exception e) {
385                            _log.error(e, e);
386                    }
387            }
388    
389            private boolean _hasAudio(FileVersion fileVersion) throws Exception {
390                    if (!isSupported(fileVersion)) {
391                            return false;
392                    }
393    
394                    return hasPreviews(fileVersion);
395            }
396    
397            private void _queueGeneration(FileVersion fileVersion) {
398                    if (_fileVersionIds.contains(fileVersion.getFileVersionId()) ||
399                            !isSupported(fileVersion)) {
400    
401                            return;
402                    }
403    
404                    _fileVersionIds.add(fileVersion.getFileVersionId());
405    
406                    if (PropsValues.DL_FILE_ENTRY_PROCESSORS_TRIGGER_SYNCHRONOUSLY) {
407                            try {
408                                    MessageBusUtil.sendSynchronousMessage(
409                                            DestinationNames.DOCUMENT_LIBRARY_AUDIO_PROCESSOR,
410                                            fileVersion);
411                            }
412                            catch (MessageBusException mbe) {
413                                    if (_log.isWarnEnabled()) {
414                                            _log.warn(mbe, mbe);
415                                    }
416                            }
417                    }
418                    else {
419                            MessageBusUtil.sendMessage(
420                                    DestinationNames.DOCUMENT_LIBRARY_AUDIO_PROCESSOR, fileVersion);
421                    }
422            }
423    
424            private static final String[] _PREVIEW_TYPES =
425                    PropsValues.DL_FILE_ENTRY_PREVIEW_AUDIO_CONTAINERS;
426    
427            private static Log _log = LogFactoryUtil.getLog(AudioProcessorImpl.class);
428    
429            private Set<String> _audioMimeTypes = SetUtil.fromArray(
430                    PropsValues.DL_FILE_ENTRY_PREVIEW_AUDIO_MIME_TYPES);
431            private List<Long> _fileVersionIds = new Vector<Long>();
432    
433            private static class LiferayAudioProcessCallable
434                    implements ProcessCallable<String> {
435    
436                    public LiferayAudioProcessCallable(
437                            String serverId, String liferayHome,
438                            Map<String, String> customLogSettings, String inputURL,
439                            String outputURL, String audioContainer,
440                            Properties audioProperties) {
441    
442                            _serverId = serverId;
443                            _liferayHome = liferayHome;
444                            _customLogSettings = customLogSettings;
445                            _inputURL = inputURL;
446                            _outputURL = outputURL;
447                            _audioContainer = audioContainer;
448                            _audioProperties = audioProperties;
449                    }
450    
451                    @Override
452                    public String call() throws ProcessException {
453                            Properties systemProperties = System.getProperties();
454    
455                            SystemEnv.setProperties(systemProperties);
456    
457                            Class<?> clazz = getClass();
458    
459                            ClassLoader classLoader = clazz.getClassLoader();
460    
461                            Log4JUtil.initLog4J(
462                                    _serverId, _liferayHome, classLoader, new Log4jLogFactoryImpl(),
463                                    _customLogSettings);
464    
465                            try {
466                                    LiferayConverter liferayConverter = new LiferayAudioConverter(
467                                            _inputURL, _outputURL, _audioContainer, _audioProperties);
468    
469                                    liferayConverter.convert();
470                            }
471                            catch (Exception e) {
472                                    throw new ProcessException(e);
473                            }
474    
475                            return StringPool.BLANK;
476                    }
477    
478                    private static final long serialVersionUID = 1L;
479    
480                    private String _audioContainer;
481                    private Properties _audioProperties;
482                    private Map<String, String> _customLogSettings;
483                    private String _inputURL;
484                    private String _liferayHome;
485                    private String _outputURL;
486                    private String _serverId;
487    
488            }
489    
490    }