/* * Copyright (c) 2006-2014 Nuxeo SA (http://nuxeo.com/) and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Florent Guillaume */ package org.nuxeo.ecm.core.convert.service; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.api.ClientException; import org.nuxeo.ecm.core.api.blobholder.BlobHolder; import org.nuxeo.ecm.core.convert.api.ConversionException; import org.nuxeo.ecm.core.convert.api.ConversionService; import org.nuxeo.ecm.core.convert.api.ConverterCheckResult; import org.nuxeo.ecm.core.convert.api.ConverterNotAvailable; import org.nuxeo.ecm.core.convert.api.ConverterNotRegistered; import org.nuxeo.ecm.core.convert.cache.CacheKeyGenerator; import org.nuxeo.ecm.core.convert.cache.ConversionCacheHolder; import org.nuxeo.ecm.core.convert.cache.GCTask; import org.nuxeo.ecm.core.convert.extension.ChainedConverter; import org.nuxeo.ecm.core.convert.extension.Converter; import org.nuxeo.ecm.core.convert.extension.ConverterDescriptor; import org.nuxeo.ecm.core.convert.extension.ExternalConverter; import org.nuxeo.ecm.core.convert.extension.GlobalConfigDescriptor; import org.nuxeo.runtime.model.ComponentContext; import org.nuxeo.runtime.model.ComponentInstance; import org.nuxeo.runtime.model.DefaultComponent; /** * Runtime Component that also provides the POJO implementation of the * {@link ConversionService}. * * @author tiry */ public class ConversionServiceImpl extends DefaultComponent implements ConversionService { protected static final Log log = LogFactory.getLog(ConversionServiceImpl.class); public static final String CONVERTER_EP = "converter"; public static final String CONFIG_EP = "configuration"; protected final Map<String, ConverterDescriptor> converterDescriptors = new HashMap<>(); protected final MimeTypeTranslationHelper translationHelper = new MimeTypeTranslationHelper(); protected final GlobalConfigDescriptor config = new GlobalConfigDescriptor(); protected static ConversionServiceImpl self; protected Thread gcThread; @Override public void activate(ComponentContext context) throws Exception { converterDescriptors.clear(); translationHelper.clear(); self = this; } @Override public void deactivate(ComponentContext context) throws Exception { if (config.isCacheEnabled()) { ConversionCacheHolder.deleteCache(); } self = null; converterDescriptors.clear(); translationHelper.clear(); } /** * Component implementation. */ @Override public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) throws Exception { if (CONVERTER_EP.equals(extensionPoint)) { ConverterDescriptor desc = (ConverterDescriptor) contribution; registerConverter(desc); } else if (CONFIG_EP.equals(extensionPoint)) { GlobalConfigDescriptor desc = (GlobalConfigDescriptor) contribution; config.update(desc); } else { log.error("Unable to handle unknown extensionPoint " + extensionPoint); } } @Override public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) throws Exception { } /* Component API */ public static Converter getConverter(String converterName) { ConverterDescriptor desc = self.converterDescriptors.get(converterName); if (desc == null) { return null; } return desc.getConverterInstance(); } public static ConverterDescriptor getConverterDescriptor( String converterName) { return self.converterDescriptors.get(converterName); } public static long getGCIntervalInMinutes() { return self.config.getGCInterval(); } public static void setGCIntervalInMinutes(long interval) { self.config.setGCInterval(interval); } public static void registerConverter(ConverterDescriptor desc) { if (self.converterDescriptors.containsKey(desc.getConverterName())) { ConverterDescriptor existing = self.converterDescriptors.get(desc.getConverterName()); desc = existing.merge(desc); } try { desc.initConverter(); } catch (Exception e) { log.error("Unable to init converter " + desc.getConverterName(), e); return; } self.translationHelper.addConverter(desc); self.converterDescriptors.put(desc.getConverterName(), desc); } public static int getMaxCacheSizeInKB() { return self.config.getDiskCacheSize(); } public static void setMaxCacheSizeInKB(int size) { self.config.setDiskCacheSize(size); } public static boolean isCacheEnabled() { return self.config.isCacheEnabled(); } public static String getCacheBasePath() { return self.config.getCachingDirectory(); } /* Service API */ @Override public List<String> getRegistredConverters() { List<String> converterNames = new ArrayList<>(); converterNames.addAll(converterDescriptors.keySet()); return converterNames; } @Override public BlobHolder convert(String converterName, BlobHolder blobHolder, Map<String, Serializable> parameters) throws ConversionException { // exist if not registered ConverterCheckResult check = isConverterAvailable(converterName); if (!check.isAvailable()) { // exist is not installed / configured throw new ConverterNotAvailable(converterName); } ConverterDescriptor desc = converterDescriptors.get(converterName); if (desc == null) { throw new ConversionException("Converter " + converterName + " can not be found"); } String cacheKey = CacheKeyGenerator.computeKey(converterName, blobHolder, parameters); BlobHolder cachedResult = ConversionCacheHolder.getFromCache(cacheKey); if (cachedResult != null) { return cachedResult; } else { Converter converter = desc.getConverterInstance(); BlobHolder result = converter.convert(blobHolder, parameters); if (config.isCacheEnabled()) { ConversionCacheHolder.addToCache(cacheKey, result); } return result; } } @Override public BlobHolder convertToMimeType(String destinationMimeType, BlobHolder blobHolder, Map<String, Serializable> parameters) throws ConversionException { String srcMt; try { srcMt = blobHolder.getBlob().getMimeType(); } catch (ClientException e) { throw new ConversionException( "error while trying to determine converter name", e); } String converterName = translationHelper.getConverterName(srcMt, destinationMimeType); if (converterName == null) { throw new ConversionException("Cannot find converter from type " + srcMt + " to type " + destinationMimeType); } return convert(converterName, blobHolder, parameters); } @Override public List<String> getConverterNames(String sourceMimeType, String destinationMimeType) { return translationHelper.getConverterNames(sourceMimeType, destinationMimeType); } @Override public String getConverterName(String sourceMimeType, String destinationMimeType) { List<String> converterNames = getConverterNames(sourceMimeType, destinationMimeType); if (!converterNames.isEmpty()) { return converterNames.get(converterNames.size() - 1); } return null; } @Override public ConverterCheckResult isConverterAvailable(String converterName) throws ConversionException { return isConverterAvailable(converterName, false); } protected final Map<String, ConverterCheckResult> checkResultCache = new HashMap<>(); @Override public ConverterCheckResult isConverterAvailable(String converterName, boolean refresh) throws ConversionException { if (!refresh) { if (checkResultCache.containsKey(converterName)) { return checkResultCache.get(converterName); } } ConverterDescriptor descriptor = converterDescriptors.get(converterName); if (descriptor == null) { throw new ConverterNotRegistered(converterName); } Converter converter = descriptor.getConverterInstance(); ConverterCheckResult result; if (converter instanceof ExternalConverter) { ExternalConverter exConverter = (ExternalConverter) converter; result = exConverter.isConverterAvailable(); } else if (converter instanceof ChainedConverter) { ChainedConverter chainedConverter = (ChainedConverter) converter; result = new ConverterCheckResult(); if (chainedConverter.isSubConvertersBased()) { for (String subConverterName : chainedConverter.getSubConverters()) { result = isConverterAvailable(subConverterName, refresh); if (!result.isAvailable()) { break; } } } } else { // return success since there is nothing to test result = new ConverterCheckResult(); } result.setSupportedInputMimeTypes(descriptor.getSourceMimeTypes()); checkResultCache.put(converterName, result); return result; } @Override public boolean isSourceMimeTypeSupported(String converterName, String sourceMimeType) { return getConverterDescriptor(converterName).getSourceMimeTypes().contains( sourceMimeType); } @Override public <T> T getAdapter(Class<T> adapter) { if (adapter.isAssignableFrom(MimeTypeTranslationHelper.class)) { return adapter.cast(translationHelper); } return super.getAdapter(adapter); } @Override public void applicationStarted(ComponentContext context) throws Exception { startGC(); } protected void startGC() { log.debug("CasheCGTaskActivator activated starting GC thread"); gcThread = new Thread(new GCTask(), "Nuxeo-Convert-GC"); gcThread.setDaemon(true); gcThread.start(); log.debug("GC Thread started"); } public void endGC() { log.debug("Stopping GC Thread"); gcThread.interrupt(); gcThread = null; } }