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