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.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                    }
330                    finally {
331                            StreamUtil.cleanUp(inputStream);
332    
333                            _fileVersionIds.remove(destinationFileVersion.getFileVersionId());
334    
335                            for (int i = 0; i < previewTempFiles.length; i++) {
336                                    FileUtil.delete(previewTempFiles[i]);
337                            }
338    
339                            FileUtil.delete(audioTempFile);
340                    }
341            }
342    
343            private void _generateAudioXuggler(
344                            FileVersion fileVersion, File srcFile, File destFile,
345                            String containerType)
346                    throws Exception {
347    
348                    if (hasPreview(fileVersion, containerType)) {
349                            return;
350                    }
351    
352                    StopWatch stopWatch = new StopWatch();
353    
354                    stopWatch.start();
355    
356                    try {
357                            if (PropsValues.DL_FILE_ENTRY_PREVIEW_FORK_PROCESS_ENABLED) {
358                                    ProcessCallable<String> processCallable =
359                                            new LiferayAudioProcessCallable(
360                                                    ServerDetector.getServerId(),
361                                                    PropsUtil.get(PropsKeys.LIFERAY_HOME),
362                                                    Log4JUtil.getCustomLogSettings(), srcFile, destFile,
363                                                    containerType,
364                                                    PropsUtil.getProperties(
365                                                            PropsKeys.DL_FILE_ENTRY_PREVIEW_AUDIO, false));
366    
367                                    ProcessChannel<String> processChannel =
368                                            ProcessExecutorUtil.execute(
369                                                    ClassPathUtil.getPortalProcessConfig(),
370                                                    processCallable);
371    
372                                    Future<String> future =
373                                            processChannel.getProcessNoticeableFuture();
374    
375                                    String processIdentity = String.valueOf(
376                                            fileVersion.getFileVersionId());
377    
378                                    futures.put(processIdentity, future);
379    
380                                    future.get();
381                            }
382                            else {
383                                    LiferayConverter liferayConverter = new LiferayAudioConverter(
384                                            srcFile.getCanonicalPath(), destFile.getCanonicalPath(),
385                                            containerType,
386                                            PropsUtil.getProperties(
387                                                    PropsKeys.DL_FILE_ENTRY_PREVIEW_AUDIO, false));
388    
389                                    liferayConverter.convert();
390                            }
391                    }
392                    catch (CancellationException ce) {
393                            if (_log.isInfoEnabled()) {
394                                    _log.info(
395                                            "Cancellation received for " +
396                                                    fileVersion.getFileVersionId() + " " +
397                                                            fileVersion.getTitle());
398                            }
399                    }
400                    catch (Exception e) {
401                            _log.error(e, e);
402                    }
403    
404                    addFileToStore(
405                            fileVersion.getCompanyId(), PREVIEW_PATH,
406                            getPreviewFilePath(fileVersion, containerType), destFile);
407    
408                    if (_log.isInfoEnabled()) {
409                            _log.info(
410                                    "Xuggler generated a " + containerType + " preview audio for " +
411                                            fileVersion.getFileVersionId() + " in " +
412                                                    stopWatch.getTime() + "ms");
413                    }
414            }
415    
416            private void _generateAudioXuggler(
417                    FileVersion fileVersion, File srcFile, File[] destFiles) {
418    
419                    try {
420                            for (int i = 0; i < destFiles.length; i++) {
421                                    _generateAudioXuggler(
422                                            fileVersion, srcFile, destFiles[i], _PREVIEW_TYPES[i]);
423                            }
424                    }
425                    catch (Exception e) {
426                            _log.error(e, e);
427                    }
428            }
429    
430            private boolean _hasAudio(FileVersion fileVersion) throws Exception {
431                    if (!isSupported(fileVersion)) {
432                            return false;
433                    }
434    
435                    return hasPreviews(fileVersion);
436            }
437    
438            private void _queueGeneration(
439                    FileVersion sourceFileVersion, FileVersion destinationFileVersion) {
440    
441                    if (_fileVersionIds.contains(
442                                    destinationFileVersion.getFileVersionId()) ||
443                            !isSupported(destinationFileVersion)) {
444    
445                            return;
446                    }
447    
448                    _fileVersionIds.add(destinationFileVersion.getFileVersionId());
449    
450                    sendGenerationMessage(
451                            DestinationNames.DOCUMENT_LIBRARY_AUDIO_PROCESSOR,
452                            sourceFileVersion, destinationFileVersion);
453            }
454    
455            private static final String[] _PREVIEW_TYPES =
456                    PropsValues.DL_FILE_ENTRY_PREVIEW_AUDIO_CONTAINERS;
457    
458            private static final Log _log = LogFactoryUtil.getLog(
459                    AudioProcessorImpl.class);
460    
461            private final Set<String> _audioMimeTypes = SetUtil.fromArray(
462                    PropsValues.DL_FILE_ENTRY_PREVIEW_AUDIO_MIME_TYPES);
463            private final List<Long> _fileVersionIds = new Vector<>();
464    
465            private static class LiferayAudioProcessCallable
466                    implements ProcessCallable<String> {
467    
468                    public LiferayAudioProcessCallable(
469                            String serverId, String liferayHome,
470                            Map<String, String> customLogSettings, File inputFile,
471                            File outputFile, String audioContainer,
472                            Properties audioProperties) {
473    
474                            _serverId = serverId;
475                            _liferayHome = liferayHome;
476                            _customLogSettings = customLogSettings;
477                            _inputFile = inputFile;
478                            _outputFile = outputFile;
479                            _audioContainer = audioContainer;
480                            _audioProperties = audioProperties;
481                    }
482    
483                    @Override
484                    public String call() throws ProcessException {
485                            XugglerAutoInstallHelper.installNativeLibraries();
486    
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 final String _audioContainer;
517                    private final Properties _audioProperties;
518                    private final Map<String, String> _customLogSettings;
519    
520                    @InputResource
521                    private final File _inputFile;
522    
523                    private final String _liferayHome;
524    
525                    @OutputResource
526                    private final File _outputFile;
527    
528                    private final String _serverId;
529    
530            }
531    
532    }