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.log.Log;
020    import com.liferay.portal.kernel.log.LogFactoryUtil;
021    import com.liferay.portal.kernel.messaging.DestinationNames;
022    import com.liferay.portal.kernel.process.ClassPathUtil;
023    import com.liferay.portal.kernel.process.ProcessCallable;
024    import com.liferay.portal.kernel.process.ProcessChannel;
025    import com.liferay.portal.kernel.process.ProcessException;
026    import com.liferay.portal.kernel.process.ProcessExecutorUtil;
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.xml.Element;
038    import com.liferay.portal.kernel.xuggler.XugglerUtil;
039    import com.liferay.portal.log.Log4jLogFactoryImpl;
040    import com.liferay.portal.repository.liferayrepository.model.LiferayFileVersion;
041    import com.liferay.portal.util.PropsUtil;
042    import com.liferay.portal.util.PropsValues;
043    import com.liferay.portlet.documentlibrary.exception.NoSuchFileEntryException;
044    import com.liferay.portlet.documentlibrary.model.DLProcessorConstants;
045    import com.liferay.portlet.exportimport.lar.PortletDataContext;
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 String getType() {
131                    return DLProcessorConstants.AUDIO_PROCESSOR;
132            }
133    
134            @Override
135            public boolean hasAudio(FileVersion fileVersion) {
136                    boolean hasAudio = false;
137    
138                    try {
139                            hasAudio = _hasAudio(fileVersion);
140    
141                            if (!hasAudio && isSupported(fileVersion)) {
142                                    _queueGeneration(null, fileVersion);
143                            }
144                    }
145                    catch (Exception e) {
146                            _log.error(e, e);
147                    }
148    
149                    return hasAudio;
150            }
151    
152            @Override
153            public boolean isAudioSupported(FileVersion fileVersion) {
154                    return isSupported(fileVersion);
155            }
156    
157            @Override
158            public boolean isAudioSupported(String mimeType) {
159                    return isSupported(mimeType);
160            }
161    
162            @Override
163            public boolean isSupported(String mimeType) {
164                    if (_audioMimeTypes.contains(mimeType) && XugglerUtil.isEnabled()) {
165                            return true;
166                    }
167    
168                    return false;
169            }
170    
171            @Override
172            public void trigger(
173                    FileVersion sourceFileVersion, FileVersion destinationFileVersion) {
174    
175                    super.trigger(sourceFileVersion, destinationFileVersion);
176    
177                    _queueGeneration(sourceFileVersion, destinationFileVersion);
178            }
179    
180            @Override
181            protected void doExportGeneratedFiles(
182                            PortletDataContext portletDataContext, FileEntry fileEntry,
183                            Element fileEntryElement)
184                    throws Exception {
185    
186                    exportPreviews(portletDataContext, fileEntry, fileEntryElement);
187            }
188    
189            @Override
190            protected void doImportGeneratedFiles(
191                            PortletDataContext portletDataContext, FileEntry fileEntry,
192                            FileEntry importedFileEntry, Element fileEntryElement)
193                    throws Exception {
194    
195                    importPreviews(
196                            portletDataContext, fileEntry, importedFileEntry, fileEntryElement);
197            }
198    
199            protected void exportPreviews(
200                            PortletDataContext portletDataContext, FileEntry fileEntry,
201                            Element fileEntryElement)
202                    throws Exception {
203    
204                    FileVersion fileVersion = fileEntry.getFileVersion();
205    
206                    if (!isSupported(fileVersion) || !hasPreviews(fileVersion)) {
207                            return;
208                    }
209    
210                    if (!portletDataContext.isPerformDirectBinaryImport()) {
211                            if ((_PREVIEW_TYPES.length == 0) || (_PREVIEW_TYPES.length > 2)) {
212                                    return;
213                            }
214    
215                            for (String previewType : _PREVIEW_TYPES) {
216                                    if (previewType.equals("mp3") || previewType.equals("ogg")) {
217                                            exportPreview(
218                                                    portletDataContext, fileEntry, fileEntryElement,
219                                                    "audio", previewType);
220                                    }
221                            }
222                    }
223            }
224    
225            @Override
226            protected List<Long> getFileVersionIds() {
227                    return _fileVersionIds;
228            }
229    
230            @Override
231            protected String getPreviewType(FileVersion fileVersion) {
232                    return _PREVIEW_TYPES[0];
233            }
234    
235            @Override
236            protected String[] getPreviewTypes() {
237                    return _PREVIEW_TYPES;
238            }
239    
240            @Override
241            protected String getThumbnailType(FileVersion fileVersion) {
242                    return null;
243            }
244    
245            protected void importPreviews(
246                            PortletDataContext portletDataContext, FileEntry fileEntry,
247                            FileEntry importedFileEntry, Element fileEntryElement)
248                    throws Exception {
249    
250                    if ((_PREVIEW_TYPES.length == 0) || (_PREVIEW_TYPES.length > 2)) {
251                            return;
252                    }
253    
254                    for (String previewType : _PREVIEW_TYPES) {
255                            if (previewType.equals("mp3") || previewType.equals("ogg")) {
256                                    importPreview(
257                                            portletDataContext, fileEntry, importedFileEntry,
258                                            fileEntryElement, "audio", previewType);
259                            }
260                    }
261            }
262    
263            private void _generateAudio(
264                            FileVersion sourceFileVersion, FileVersion destinationFileVersion)
265                    throws Exception {
266    
267                    String tempFileId = DLUtil.getTempFileId(
268                            destinationFileVersion.getFileEntryId(),
269                            destinationFileVersion.getVersion());
270    
271                    File[] previewTempFiles = new File[_PREVIEW_TYPES.length];
272    
273                    for (int i = 0; i < _PREVIEW_TYPES.length; i++) {
274                            previewTempFiles[i] = getPreviewTempFile(
275                                    tempFileId, _PREVIEW_TYPES[i]);
276                    }
277    
278                    File audioTempFile = null;
279    
280                    InputStream inputStream = null;
281    
282                    try {
283                            if (sourceFileVersion != null) {
284                                    copy(sourceFileVersion, destinationFileVersion);
285    
286                                    return;
287                            }
288    
289                            if (!XugglerUtil.isEnabled() || _hasAudio(destinationFileVersion)) {
290                                    return;
291                            }
292    
293                            audioTempFile = FileUtil.createTempFile(
294                                    destinationFileVersion.getExtension());
295    
296                            if (!hasPreviews(destinationFileVersion)) {
297                                    File file = null;
298    
299                                    if (destinationFileVersion instanceof LiferayFileVersion) {
300                                            try {
301                                                    LiferayFileVersion liferayFileVersion =
302                                                            (LiferayFileVersion)destinationFileVersion;
303    
304                                                    file = liferayFileVersion.getFile(false);
305                                            }
306                                            catch (UnsupportedOperationException uoe) {
307                                            }
308                                    }
309    
310                                    if (file == null) {
311                                            inputStream = destinationFileVersion.getContentStream(
312                                                    false);
313    
314                                            FileUtil.write(audioTempFile, inputStream);
315    
316                                            file = audioTempFile;
317                                    }
318    
319                                    try {
320                                            _generateAudioXuggler(
321                                                    destinationFileVersion, file, previewTempFiles);
322                                    }
323                                    catch (Exception e) {
324                                            _log.error(e, e);
325                                    }
326                            }
327                    }
328                    catch (NoSuchFileEntryException nsfee) {
329                            if (_log.isDebugEnabled()) {
330                                    _log.debug(nsfee, nsfee);
331                            }
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 final Log _log = LogFactoryUtil.getLog(
462                    AudioProcessorImpl.class);
463    
464            private final Set<String> _audioMimeTypes = SetUtil.fromArray(
465                    PropsValues.DL_FILE_ENTRY_PREVIEW_AUDIO_MIME_TYPES);
466            private final List<Long> _fileVersionIds = new Vector<>();
467    
468            private static class LiferayAudioProcessCallable
469                    implements ProcessCallable<String> {
470    
471                    public LiferayAudioProcessCallable(
472                            String serverId, String liferayHome,
473                            Map<String, String> customLogSettings, File inputFile,
474                            File outputFile, String audioContainer,
475                            Properties audioProperties) {
476    
477                            _serverId = serverId;
478                            _liferayHome = liferayHome;
479                            _customLogSettings = customLogSettings;
480                            _inputFile = inputFile;
481                            _outputFile = outputFile;
482                            _audioContainer = audioContainer;
483                            _audioProperties = audioProperties;
484                    }
485    
486                    @Override
487                    public String call() throws ProcessException {
488                            XugglerAutoInstallHelper.installNativeLibraries();
489    
490                            Properties systemProperties = System.getProperties();
491    
492                            SystemEnv.setProperties(systemProperties);
493    
494                            Class<?> clazz = getClass();
495    
496                            ClassLoader classLoader = clazz.getClassLoader();
497    
498                            Log4JUtil.initLog4J(
499                                    _serverId, _liferayHome, classLoader, new Log4jLogFactoryImpl(),
500                                    _customLogSettings);
501    
502                            try {
503                                    LiferayConverter liferayConverter = new LiferayAudioConverter(
504                                            _inputFile.getCanonicalPath(),
505                                            _outputFile.getCanonicalPath(), _audioContainer,
506                                            _audioProperties);
507    
508                                    liferayConverter.convert();
509                            }
510                            catch (Exception e) {
511                                    throw new ProcessException(e);
512                            }
513    
514                            return StringPool.BLANK;
515                    }
516    
517                    private static final long serialVersionUID = 1L;
518    
519                    private final String _audioContainer;
520                    private final Properties _audioProperties;
521                    private final Map<String, String> _customLogSettings;
522    
523                    @InputResource
524                    private final File _inputFile;
525    
526                    private final String _liferayHome;
527    
528                    @OutputResource
529                    private final File _outputFile;
530    
531                    private final String _serverId;
532    
533            }
534    
535    }