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.document.library.kernel.exception.NoSuchFileEntryException;
018    import com.liferay.document.library.kernel.model.DLProcessorConstants;
019    import com.liferay.document.library.kernel.util.AudioProcessor;
020    import com.liferay.document.library.kernel.util.DLPreviewableProcessor;
021    import com.liferay.document.library.kernel.util.DLUtil;
022    import com.liferay.exportimport.kernel.lar.PortletDataContext;
023    import com.liferay.portal.fabric.InputResource;
024    import com.liferay.portal.fabric.OutputResource;
025    import com.liferay.portal.kernel.log.Log;
026    import com.liferay.portal.kernel.log.LogFactoryUtil;
027    import com.liferay.portal.kernel.messaging.DestinationNames;
028    import com.liferay.portal.kernel.process.ClassPathUtil;
029    import com.liferay.portal.kernel.process.ProcessCallable;
030    import com.liferay.portal.kernel.process.ProcessChannel;
031    import com.liferay.portal.kernel.process.ProcessException;
032    import com.liferay.portal.kernel.process.ProcessExecutorUtil;
033    import com.liferay.portal.kernel.repository.model.FileEntry;
034    import com.liferay.portal.kernel.repository.model.FileVersion;
035    import com.liferay.portal.kernel.util.FileUtil;
036    import com.liferay.portal.kernel.util.PropsKeys;
037    import com.liferay.portal.kernel.util.ServerDetector;
038    import com.liferay.portal.kernel.util.SetUtil;
039    import com.liferay.portal.kernel.util.StreamUtil;
040    import com.liferay.portal.kernel.util.StringBundler;
041    import com.liferay.portal.kernel.util.StringPool;
042    import com.liferay.portal.kernel.util.SystemEnv;
043    import com.liferay.portal.kernel.xml.Element;
044    import com.liferay.portal.kernel.xuggler.XugglerUtil;
045    import com.liferay.portal.log.Log4jLogFactoryImpl;
046    import com.liferay.portal.repository.liferayrepository.model.LiferayFileVersion;
047    import com.liferay.portal.util.PropsUtil;
048    import com.liferay.portal.util.PropsValues;
049    import com.liferay.util.log4j.Log4JUtil;
050    
051    import java.io.File;
052    import java.io.InputStream;
053    
054    import java.util.List;
055    import java.util.Map;
056    import java.util.Properties;
057    import java.util.Set;
058    import java.util.Vector;
059    import java.util.concurrent.CancellationException;
060    import java.util.concurrent.Future;
061    
062    import org.apache.commons.lang.time.StopWatch;
063    
064    /**
065     * @author Juan Gonz??lez
066     * @author Sergio Gonz??lez
067     * @author Mika Koivisto
068     * @author Ivica Cardic
069     */
070    public class AudioProcessorImpl
071            extends DLPreviewableProcessor implements AudioProcessor {
072    
073            @Override
074            public void afterPropertiesSet() {
075                    boolean valid = true;
076    
077                    if ((_PREVIEW_TYPES.length == 0) || (_PREVIEW_TYPES.length > 2)) {
078                            valid = false;
079                    }
080                    else {
081                            for (String previewType : _PREVIEW_TYPES) {
082                                    if (!previewType.equals("mp3") && !previewType.equals("ogg")) {
083                                            valid = false;
084    
085                                            break;
086                                    }
087                            }
088                    }
089    
090                    if (!valid && _log.isWarnEnabled()) {
091                            StringBundler sb = new StringBundler(5);
092    
093                            sb.append("Liferay is incorrectly configured to generate video ");
094                            sb.append("previews using video containers other than MP3 or ");
095                            sb.append("OGG. Please change the property ");
096                            sb.append(PropsKeys.DL_FILE_ENTRY_PREVIEW_AUDIO_CONTAINERS);
097                            sb.append(" in portal-ext.properties.");
098    
099                            _log.warn(sb.toString());
100                    }
101    
102                    FileUtil.mkdirs(PREVIEW_TMP_PATH);
103            }
104    
105            @Override
106            public void generateAudio(
107                            FileVersion sourceFileVersion, FileVersion destinationFileVersion)
108                    throws Exception {
109    
110                    _generateAudio(sourceFileVersion, destinationFileVersion);
111            }
112    
113            @Override
114            public Set<String> getAudioMimeTypes() {
115                    return _audioMimeTypes;
116            }
117    
118            @Override
119            public InputStream getPreviewAsStream(FileVersion fileVersion, String type)
120                    throws Exception {
121    
122                    return doGetPreviewAsStream(fileVersion, type);
123            }
124    
125            @Override
126            public long getPreviewFileSize(FileVersion fileVersion, String type)
127                    throws Exception {
128    
129                    return doGetPreviewFileSize(fileVersion, type);
130            }
131    
132            @Override
133            public String getType() {
134                    return DLProcessorConstants.AUDIO_PROCESSOR;
135            }
136    
137            @Override
138            public boolean hasAudio(FileVersion fileVersion) {
139                    boolean hasAudio = false;
140    
141                    try {
142                            hasAudio = _hasAudio(fileVersion);
143    
144                            if (!hasAudio && isSupported(fileVersion)) {
145                                    _queueGeneration(null, fileVersion);
146                            }
147                    }
148                    catch (Exception e) {
149                            _log.error(e, e);
150                    }
151    
152                    return hasAudio;
153            }
154    
155            @Override
156            public boolean isAudioSupported(FileVersion fileVersion) {
157                    return isSupported(fileVersion);
158            }
159    
160            @Override
161            public boolean isAudioSupported(String mimeType) {
162                    return isSupported(mimeType);
163            }
164    
165            @Override
166            public boolean isSupported(String mimeType) {
167                    if (_audioMimeTypes.contains(mimeType) && XugglerUtil.isEnabled()) {
168                            return true;
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                            if (_log.isDebugEnabled()) {
333                                    _log.debug(nsfee, nsfee);
334                            }
335                    }
336                    finally {
337                            StreamUtil.cleanUp(inputStream);
338    
339                            _fileVersionIds.remove(destinationFileVersion.getFileVersionId());
340    
341                            for (int i = 0; i < previewTempFiles.length; i++) {
342                                    FileUtil.delete(previewTempFiles[i]);
343                            }
344    
345                            FileUtil.delete(audioTempFile);
346                    }
347            }
348    
349            private void _generateAudioXuggler(
350                            FileVersion fileVersion, File srcFile, File destFile,
351                            String containerType)
352                    throws Exception {
353    
354                    if (hasPreview(fileVersion, containerType)) {
355                            return;
356                    }
357    
358                    StopWatch stopWatch = new StopWatch();
359    
360                    stopWatch.start();
361    
362                    try {
363                            if (PropsValues.DL_FILE_ENTRY_PREVIEW_FORK_PROCESS_ENABLED) {
364                                    ProcessCallable<String> processCallable =
365                                            new LiferayAudioProcessCallable(
366                                                    ServerDetector.getServerId(),
367                                                    PropsUtil.get(PropsKeys.LIFERAY_HOME),
368                                                    Log4JUtil.getCustomLogSettings(), srcFile, destFile,
369                                                    containerType,
370                                                    PropsUtil.getProperties(
371                                                            PropsKeys.DL_FILE_ENTRY_PREVIEW_AUDIO, false));
372    
373                                    ProcessChannel<String> processChannel =
374                                            ProcessExecutorUtil.execute(
375                                                    ClassPathUtil.getPortalProcessConfig(),
376                                                    processCallable);
377    
378                                    Future<String> future =
379                                            processChannel.getProcessNoticeableFuture();
380    
381                                    String processIdentity = String.valueOf(
382                                            fileVersion.getFileVersionId());
383    
384                                    futures.put(processIdentity, future);
385    
386                                    future.get();
387                            }
388                            else {
389                                    LiferayConverter liferayConverter = new LiferayAudioConverter(
390                                            srcFile.getCanonicalPath(), destFile.getCanonicalPath(),
391                                            containerType,
392                                            PropsUtil.getProperties(
393                                                    PropsKeys.DL_FILE_ENTRY_PREVIEW_AUDIO, false));
394    
395                                    liferayConverter.convert();
396                            }
397                    }
398                    catch (CancellationException ce) {
399                            if (_log.isInfoEnabled()) {
400                                    _log.info(
401                                            "Cancellation received for " +
402                                                    fileVersion.getFileVersionId() + " " +
403                                                            fileVersion.getTitle());
404                            }
405                    }
406                    catch (Exception e) {
407                            _log.error(e, e);
408                    }
409    
410                    addFileToStore(
411                            fileVersion.getCompanyId(), PREVIEW_PATH,
412                            getPreviewFilePath(fileVersion, containerType), destFile);
413    
414                    if (_log.isInfoEnabled()) {
415                            _log.info(
416                                    "Xuggler generated a " + containerType + " preview audio for " +
417                                            fileVersion.getFileVersionId() + " in " +
418                                                    stopWatch.getTime() + "ms");
419                    }
420            }
421    
422            private void _generateAudioXuggler(
423                    FileVersion fileVersion, File srcFile, File[] destFiles) {
424    
425                    try {
426                            for (int i = 0; i < destFiles.length; i++) {
427                                    _generateAudioXuggler(
428                                            fileVersion, srcFile, destFiles[i], _PREVIEW_TYPES[i]);
429                            }
430                    }
431                    catch (Exception e) {
432                            _log.error(e, e);
433                    }
434            }
435    
436            private boolean _hasAudio(FileVersion fileVersion) throws Exception {
437                    if (!isSupported(fileVersion)) {
438                            return false;
439                    }
440    
441                    return hasPreviews(fileVersion);
442            }
443    
444            private void _queueGeneration(
445                    FileVersion sourceFileVersion, FileVersion destinationFileVersion) {
446    
447                    if (_fileVersionIds.contains(
448                                    destinationFileVersion.getFileVersionId()) ||
449                            !isSupported(destinationFileVersion)) {
450    
451                            return;
452                    }
453    
454                    _fileVersionIds.add(destinationFileVersion.getFileVersionId());
455    
456                    sendGenerationMessage(
457                            DestinationNames.DOCUMENT_LIBRARY_AUDIO_PROCESSOR,
458                            sourceFileVersion, destinationFileVersion);
459            }
460    
461            private static final String[] _PREVIEW_TYPES =
462                    PropsValues.DL_FILE_ENTRY_PREVIEW_AUDIO_CONTAINERS;
463    
464            private static final Log _log = LogFactoryUtil.getLog(
465                    AudioProcessorImpl.class);
466    
467            private final Set<String> _audioMimeTypes = SetUtil.fromArray(
468                    PropsValues.DL_FILE_ENTRY_PREVIEW_AUDIO_MIME_TYPES);
469            private final List<Long> _fileVersionIds = new Vector<>();
470    
471            private static class LiferayAudioProcessCallable
472                    implements ProcessCallable<String> {
473    
474                    public LiferayAudioProcessCallable(
475                            String serverId, String liferayHome,
476                            Map<String, String> customLogSettings, File inputFile,
477                            File outputFile, String audioContainer,
478                            Properties audioProperties) {
479    
480                            _serverId = serverId;
481                            _liferayHome = liferayHome;
482                            _customLogSettings = customLogSettings;
483                            _inputFile = inputFile;
484                            _outputFile = outputFile;
485                            _audioContainer = audioContainer;
486                            _audioProperties = audioProperties;
487                    }
488    
489                    @Override
490                    public String call() throws ProcessException {
491                            XugglerAutoInstallHelper.installNativeLibraries();
492    
493                            Properties systemProperties = System.getProperties();
494    
495                            SystemEnv.setProperties(systemProperties);
496    
497                            Class<?> clazz = getClass();
498    
499                            ClassLoader classLoader = clazz.getClassLoader();
500    
501                            Log4JUtil.initLog4J(
502                                    _serverId, _liferayHome, classLoader, new Log4jLogFactoryImpl(),
503                                    _customLogSettings);
504    
505                            try {
506                                    LiferayConverter liferayConverter = new LiferayAudioConverter(
507                                            _inputFile.getCanonicalPath(),
508                                            _outputFile.getCanonicalPath(), _audioContainer,
509                                            _audioProperties);
510    
511                                    liferayConverter.convert();
512                            }
513                            catch (Exception e) {
514                                    throw new ProcessException(e);
515                            }
516    
517                            return StringPool.BLANK;
518                    }
519    
520                    private static final long serialVersionUID = 1L;
521    
522                    private final String _audioContainer;
523                    private final Properties _audioProperties;
524                    private final Map<String, String> _customLogSettings;
525    
526                    @InputResource
527                    private final File _inputFile;
528    
529                    private final String _liferayHome;
530    
531                    @OutputResource
532                    private final File _outputFile;
533    
534                    private final String _serverId;
535    
536            }
537    
538    }