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