001/*
002 * (C) Copyright 2006-2015 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 */
020
021package org.nuxeo.ecm.platform.convert.plugins;
022
023import java.io.Closeable;
024import java.io.IOException;
025import java.io.Serializable;
026import java.util.ArrayList;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030
031import org.apache.commons.io.FilenameUtils;
032import org.apache.commons.io.IOUtils;
033import org.nuxeo.common.Environment;
034import org.nuxeo.ecm.core.api.Blob;
035import org.nuxeo.ecm.core.api.CloseableFile;
036import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
037import org.nuxeo.ecm.core.convert.api.ConversionException;
038import org.nuxeo.ecm.core.convert.api.ConverterCheckResult;
039import org.nuxeo.ecm.core.convert.extension.Converter;
040import org.nuxeo.ecm.core.convert.extension.ConverterDescriptor;
041import org.nuxeo.ecm.core.convert.extension.ExternalConverter;
042import org.nuxeo.ecm.platform.commandline.executor.api.CmdParameters;
043import org.nuxeo.ecm.platform.commandline.executor.api.CommandAvailability;
044import org.nuxeo.ecm.platform.commandline.executor.api.CommandException;
045import org.nuxeo.ecm.platform.commandline.executor.api.CommandLineExecutorService;
046import org.nuxeo.ecm.platform.commandline.executor.api.CommandNotAvailable;
047import org.nuxeo.ecm.platform.commandline.executor.api.ExecResult;
048import org.nuxeo.runtime.api.Framework;
049
050/**
051 * Base class to implement {@link Converter} based on {@link CommandLineExecutorService}.
052 *
053 * @author tiry
054 */
055public abstract class CommandLineBasedConverter implements ExternalConverter {
056
057    protected static final String CMD_NAME_PARAMETER = "CommandLineName";
058
059    protected static final String TMP_PATH_PARAMETER = "TmpDirectory";
060
061    protected Map<String, String> initParameters;
062
063    /**
064     * @deprecated Since 7.4. Useless.
065     */
066    @Deprecated
067    protected CommandLineExecutorService getCommandLineService() {
068        return Framework.getService(CommandLineExecutorService.class);
069    }
070
071    public String getTmpDirectory(Map<String, Serializable> parameters) {
072        String tmp = initParameters.get(TMP_PATH_PARAMETER);
073        if (parameters != null && parameters.containsKey(TMP_PATH_PARAMETER)) {
074            tmp = (String) parameters.get(TMP_PATH_PARAMETER);
075        }
076        if (tmp == null) {
077            tmp = Environment.getDefault().getTemp().getPath();
078        }
079        return tmp;
080    }
081
082    @Override
083    public BlobHolder convert(BlobHolder blobHolder, Map<String, Serializable> parameters) throws ConversionException {
084        String commandName = getCommandName(blobHolder, parameters);
085        if (commandName == null) {
086            throw new ConversionException("Unable to determine target CommandLine name", blobHolder);
087        }
088
089        Map<String, Blob> blobParams = getCmdBlobParameters(blobHolder, parameters);
090        Map<String, String> strParams = getCmdStringParameters(blobHolder, parameters);
091
092        CmdReturn result = execOnBlob(commandName, blobParams, strParams);
093        return buildResult(result.output, result.params);
094    }
095
096    protected String getCommandName(BlobHolder blobHolder, Map<String, Serializable> parameters) {
097        String commandName = initParameters.get(CMD_NAME_PARAMETER);
098        if (parameters != null && parameters.containsKey(CMD_NAME_PARAMETER)) {
099            commandName = (String) parameters.get(CMD_NAME_PARAMETER);
100        }
101        return commandName;
102    }
103
104    /**
105     * Extracts BlobParameters.
106     */
107    protected abstract Map<String, Blob> getCmdBlobParameters(BlobHolder blobHolder,
108            Map<String, Serializable> parameters) throws ConversionException;
109
110    /**
111     * Extracts String parameters.
112     */
113    protected abstract Map<String, String> getCmdStringParameters(BlobHolder blobHolder,
114            Map<String, Serializable> parameters) throws ConversionException;
115
116    /**
117     * Builds result from commandLine output buffer.
118     */
119    protected abstract BlobHolder buildResult(List<String> cmdOutput, CmdParameters cmdParams)
120            throws ConversionException;
121
122    protected class CmdReturn {
123        protected final CmdParameters params;
124
125        protected final List<String> output;
126
127        protected CmdReturn(CmdParameters params, List<String> output) {
128            this.params = params;
129            this.output = output;
130        }
131    }
132
133    protected CmdReturn execOnBlob(String commandName, Map<String, Blob> blobParameters, Map<String, String> parameters)
134            throws ConversionException {
135        CommandLineExecutorService cles = Framework.getService(CommandLineExecutorService.class);
136        CmdParameters params = cles.getDefaultCmdParameters();
137        List<Closeable> toClose = new ArrayList<>();
138        try {
139            if (blobParameters != null) {
140                for (String blobParamName : blobParameters.keySet()) {
141                    Blob blob = blobParameters.get(blobParamName);
142                    // closed in finally block
143                    @SuppressWarnings("resource") // closed in finally
144                    CloseableFile closeable = blob.getCloseableFile(
145                            "." + FilenameUtils.getExtension(blob.getFilename()));
146                    params.addNamedParameter(blobParamName, closeable.getFile());
147                    toClose.add(closeable);
148                }
149            }
150
151            if (parameters != null) {
152                for (String paramName : parameters.keySet()) {
153                    params.addNamedParameter(paramName, parameters.get(paramName));
154                }
155            }
156
157            ExecResult result = Framework.getService(CommandLineExecutorService.class).execCommand(commandName, params);
158            if (!result.isSuccessful()) {
159                throw result.getError();
160            }
161            return new CmdReturn(params, result.getOutput());
162        } catch (CommandNotAvailable e) {
163            // XXX bubble installation instructions
164            throw new ConversionException("Unable to find targetCommand", e);
165        } catch (IOException | CommandException e) {
166            throw new ConversionException("Error while converting via CommandLineService", e);
167        } finally {
168            for (Closeable closeable : toClose) {
169                IOUtils.closeQuietly(closeable);
170            }
171        }
172    }
173
174    @Override
175    public void init(ConverterDescriptor descriptor) {
176        initParameters = descriptor.getParameters();
177        if (initParameters == null) {
178            initParameters = new HashMap<>();
179        }
180    }
181
182    @Override
183    public ConverterCheckResult isConverterAvailable() {
184        String commandName = getCommandName(null, null);
185        if (commandName == null) {
186            // can not check
187            return new ConverterCheckResult();
188        }
189
190        CommandAvailability ca = Framework.getService(CommandLineExecutorService.class)
191                                          .getCommandAvailability(commandName);
192
193        if (ca.isAvailable()) {
194            return new ConverterCheckResult();
195        } else {
196            return new ConverterCheckResult(ca.getInstallMessage(), ca.getErrorMessage());
197        }
198    }
199
200}