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