001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nuxeo - initial API and implementation
018 *
019 * $Id$
020 */
021
022package org.nuxeo.ecm.core.convert.extension;
023
024import java.io.Serializable;
025import java.util.ArrayList;
026import java.util.List;
027import java.util.Map;
028import java.util.Objects;
029
030import org.nuxeo.ecm.core.api.Blob;
031import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
032import org.nuxeo.ecm.core.convert.api.ConversionException;
033import org.nuxeo.ecm.core.convert.api.ConversionService;
034import org.nuxeo.ecm.core.convert.service.ConversionServiceImpl;
035import org.nuxeo.runtime.api.Framework;
036
037/**
038 * Specific {@link Converter} implementation that acts as a converters chain.
039 * <p>
040 * The chain can be:
041 * <ul>
042 * <li>a chain of mime-types
043 * <li>a chain of converter names
044 * </ul>
045 * <p>
046 * This depends on the properties of the descriptor.
047 *
048 * @author tiry
049 */
050public class ChainedConverter implements Converter {
051
052    protected boolean subConvertersBased;
053
054    protected List<String> steps = new ArrayList<>();
055
056    protected List<String> subConverters = new ArrayList<>();
057
058    public ChainedConverter() {
059        subConvertersBased = false;
060        subConverters = null;
061    }
062
063    public ChainedConverter(List<String> subConverters) {
064        subConvertersBased = true;
065        this.subConverters = subConverters;
066        steps = null;
067    }
068
069    @Override
070    public BlobHolder convert(BlobHolder blobHolder, Map<String, Serializable> parameters) throws ConversionException {
071
072        if (subConvertersBased) {
073            return convertBasedSubConverters(blobHolder, parameters);
074        } else {
075            return convertBasedOnMimeTypes(blobHolder, parameters);
076        }
077    }
078
079    protected BlobHolder convertBasedSubConverters(BlobHolder blobHolder, Map<String, Serializable> parameters)
080            throws ConversionException {
081        String srcMT = blobHolder.getBlob().getMimeType();
082        BlobHolder result = blobHolder;
083        for (String converterName : subConverters) {
084            ConverterDescriptor desc = ConversionServiceImpl.getConverterDescriptor(converterName);
085            if (!desc.getSourceMimeTypes().contains(srcMT)) {
086                throw new ConversionException("Conversion Chain is not well defined", blobHolder);
087            }
088            Converter converter = ConversionServiceImpl.getConverter(converterName);
089            result = converter.convert(result, parameters);
090            // Mark for deletion intermediate results
091            if (subConverters.indexOf(converterName) != subConverters.size() - 1) {
092                result.getBlobs()
093                      .stream()
094                      .map(Blob::getFile)
095                      .filter(Objects::nonNull)
096                      .forEach(file -> Framework.trackFile(file, file));
097            }
098            srcMT = desc.getDestinationMimeType();
099        }
100        return result;
101    }
102
103    /**
104     * Tries to find a chain of converters that fits the mime-types chain.
105     */
106    protected BlobHolder convertBasedOnMimeTypes(BlobHolder blobHolder, Map<String, Serializable> parameters)
107            throws ConversionException {
108        String srcMT = blobHolder.getBlob().getMimeType();
109        BlobHolder result = blobHolder;
110        for (String dstMT : steps) {
111            String converterName = Framework.getService(ConversionService.class).getConverterName(srcMT, dstMT);
112            if (converterName == null) {
113                throw new ConversionException(
114                        "Chained conversion error : unable to find converter between " + srcMT + " and " + dstMT,
115                        blobHolder);
116            }
117            Converter converter = ConversionServiceImpl.getConverter(converterName);
118            result = converter.convert(result, parameters);
119            srcMT = dstMT;
120        }
121        return result;
122    }
123
124    @Override
125    public void init(ConverterDescriptor descriptor) {
126        if (!subConvertersBased) {
127            steps.addAll(descriptor.getSteps());
128            steps.add(descriptor.getDestinationMimeType());
129        } else {
130            ConverterDescriptor fconv = ConversionServiceImpl.getConverterDescriptor(subConverters.get(0));
131            ConverterDescriptor lconv = ConversionServiceImpl.getConverterDescriptor(
132                    subConverters.get(subConverters.size() - 1));
133
134            descriptor.sourceMimeTypes = fconv.sourceMimeTypes;
135            descriptor.destinationMimeType = lconv.destinationMimeType;
136        }
137    }
138
139    public List<String> getSteps() {
140        return steps;
141    }
142
143    /**
144     * Returns the sub converters of this chained converter.
145     *
146     * @since 5.9.2
147     */
148    public List<String> getSubConverters() {
149        return subConverters;
150    }
151
152    /**
153     * Returns true if this chained converter is sub converters based, false otherwise.
154     *
155     * @since 5.9.4
156     */
157    public boolean isSubConvertersBased() {
158        return subConvertersBased;
159    }
160}