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