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