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