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 = null;
353    
354                    if (_log.isInfoEnabled()) {
355                            stopWatch = new StopWatch();
356    
357                            stopWatch.start();
358                    }
359    
360                    try {
361                            if (PropsValues.DL_FILE_ENTRY_PREVIEW_FORK_PROCESS_ENABLED) {
362                                    ProcessCallable<String> processCallable =
363                                            new LiferayAudioProcessCallable(
364                                                    ServerDetector.getServerId(),
365                                                    PropsUtil.get(PropsKeys.LIFERAY_HOME),
366                                                    Log4JUtil.getCustomLogSettings(),
367                                                    srcFile.getCanonicalPath(), destFile.getCanonicalPath(),
368                                                    containerType,
369                                                    PropsUtil.getProperties(
370                                                            PropsKeys.DL_FILE_ENTRY_PREVIEW_AUDIO, false));
371    
372                                    Future<String> future = ProcessExecutor.execute(
373                                            ClassPathUtil.getPortalClassPath(), processCallable);
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 Log _log = LogFactoryUtil.getLog(AudioProcessorImpl.class);
459    
460            private Set<String> _audioMimeTypes = SetUtil.fromArray(
461                    PropsValues.DL_FILE_ENTRY_PREVIEW_AUDIO_MIME_TYPES);
462            private List<Long> _fileVersionIds = new Vector<Long>();
463    
464            private static class LiferayAudioProcessCallable
465                    implements ProcessCallable<String> {
466    
467                    public LiferayAudioProcessCallable(
468                            String serverId, String liferayHome,
469                            Map<String, String> customLogSettings, String inputURL,
470                            String outputURL, String audioContainer,
471                            Properties audioProperties) {
472    
473                            _serverId = serverId;
474                            _liferayHome = liferayHome;
475                            _customLogSettings = customLogSettings;
476                            _inputURL = inputURL;
477                            _outputURL = outputURL;
478                            _audioContainer = audioContainer;
479                            _audioProperties = audioProperties;
480                    }
481    
482                    @Override
483                    public String call() throws ProcessException {
484                            Properties systemProperties = System.getProperties();
485    
486                            SystemEnv.setProperties(systemProperties);
487    
488                            Class<?> clazz = getClass();
489    
490                            ClassLoader classLoader = clazz.getClassLoader();
491    
492                            Log4JUtil.initLog4J(
493                                    _serverId, _liferayHome, classLoader, new Log4jLogFactoryImpl(),
494                                    _customLogSettings);
495    
496                            try {
497                                    LiferayConverter liferayConverter = new LiferayAudioConverter(
498                                            _inputURL, _outputURL, _audioContainer, _audioProperties);
499    
500                                    liferayConverter.convert();
501                            }
502                            catch (Exception e) {
503                                    throw new ProcessException(e);
504                            }
505    
506                            return StringPool.BLANK;
507                    }
508    
509                    private static final long serialVersionUID = 1L;
510    
511                    private String _audioContainer;
512                    private Properties _audioProperties;
513                    private Map<String, String> _customLogSettings;
514                    private String _inputURL;
515                    private String _liferayHome;
516                    private String _outputURL;
517                    private String _serverId;
518    
519            }
520    
521    }