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.ooomanager;
022
023import java.io.File;
024import java.io.IOException;
025import java.util.ArrayList;
026
027import org.apache.commons.io.FileUtils;
028import org.apache.commons.lang.ArrayUtils;
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031import org.artofsolving.jodconverter.OfficeDocumentConverter;
032import org.artofsolving.jodconverter.office.DefaultOfficeManagerConfiguration;
033import org.artofsolving.jodconverter.office.OfficeConnectionProtocol;
034import org.artofsolving.jodconverter.office.OfficeException;
035import org.artofsolving.jodconverter.office.OfficeManager;
036import org.artofsolving.jodconverter.office.OfficeTask;
037
038import org.nuxeo.runtime.api.Framework;
039import org.nuxeo.runtime.model.ComponentContext;
040import org.nuxeo.runtime.model.ComponentInstance;
041import org.nuxeo.runtime.model.DefaultComponent;
042
043public class OOoManagerComponent extends DefaultComponent implements OOoManagerService {
044
045    protected static final Log log = LogFactory.getLog(OOoManagerComponent.class);
046
047    private static final String CONNECTION_PROTOCOL_PROPERTY_KEY = "jod.connection.protocol";
048
049    private static final String MAX_TASKS_PER_PROCESS_PROPERTY_KEY = "jod.max.tasks.per.process";
050
051    private static final String OFFICE_HOME_PROPERTY_KEY = "jod.office.home";
052
053    private static final String TASK_EXECUTION_TIMEOUT_PROPERTY_KEY = "jod.task.execution.timeout";
054
055    private static final String TASK_QUEUE_TIMEOUT_PROPERTY_KEY = "jod.task.queue.timeout";
056
057    private static final String TEMPLATE_PROFILE_DIR_PROPERTY_KEY = "jod.template.profile.dir";
058
059    private static final String OFFICE_PIPES_PROPERTY_KEY = "jod.office.pipes";
060
061    private static final String OFFICE_PORTS_PROPERTY_KEY = "jod.office.ports";
062
063    protected static final String CONFIG_EP = "oooManagerConfig";
064
065    private static OfficeManager officeManager;
066
067    protected OOoManagerDescriptor descriptor = new OOoManagerDescriptor();
068
069    protected boolean started = false;
070
071    protected boolean starting = false;
072
073    protected boolean shutingdown = false;
074
075    public OOoManagerDescriptor getDescriptor() {
076        return descriptor;
077    }
078
079    @Override
080    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
081        if (CONFIG_EP.equals(extensionPoint)) {
082            OOoManagerDescriptor desc = (OOoManagerDescriptor) contribution;
083            descriptor = desc;
084        }
085    }
086
087    @Override
088    public void deactivate(ComponentContext context) {
089        stopOOoManager();
090    }
091
092    @Override
093    public OfficeDocumentConverter getDocumentConverter() {
094        if (isOOoManagerStarted()) {
095            return new OfficeDocumentConverter(officeManager);
096        } else {
097            log.error("OfficeManager is not started.");
098            return null;
099        }
100    }
101
102    public void executeTask(OfficeTask task) {
103        if (isOOoManagerStarted()) {
104            officeManager.execute(task);
105        } else {
106            log.error("OfficeManager is not started.");
107        }
108    }
109
110    @Override
111    public void stopOOoManager() {
112        if (isOOoManagerStarted() && !shutingdown) {
113            shutingdown = true;
114            officeManager.stop();
115            started = false;
116            shutingdown = false;
117            log.debug("Stopping ooo manager.");
118        } else {
119            log.debug("OOoManager already stopped..");
120        }
121    }
122
123    @Override
124    public void startOOoManager() throws IOException {
125        DefaultOfficeManagerConfiguration configuration = new DefaultOfficeManagerConfiguration();
126
127        starting = true;
128
129        try {
130            // Properties configuration
131            String connectionProtocol = Framework.getProperty(CONNECTION_PROTOCOL_PROPERTY_KEY);
132            if (connectionProtocol != null && !"".equals(connectionProtocol)) {
133                if (OfficeConnectionProtocol.PIPE.toString().equals(connectionProtocol)) {
134                    ConfigBuilderHelper.hackClassLoader();
135                    configuration.setConnectionProtocol(OfficeConnectionProtocol.PIPE);
136                } else if (OfficeConnectionProtocol.SOCKET.toString().equals(connectionProtocol)) {
137                    configuration.setConnectionProtocol(OfficeConnectionProtocol.SOCKET);
138                }
139            }
140            String maxTasksPerProcessProperty = Framework.getProperty(MAX_TASKS_PER_PROCESS_PROPERTY_KEY);
141            if (maxTasksPerProcessProperty != null && !"".equals(maxTasksPerProcessProperty)) {
142                Integer maxTasksPerProcess = Integer.valueOf(maxTasksPerProcessProperty);
143                configuration.setMaxTasksPerProcess(maxTasksPerProcess);
144            }
145            String officeHome = Framework.getProperty(OFFICE_HOME_PROPERTY_KEY);
146            if (officeHome != null && !"".equals(officeHome)) {
147                configuration.setOfficeHome(officeHome);
148            }
149
150            String taskExecutionTimeoutProperty = Framework.getProperty(TASK_EXECUTION_TIMEOUT_PROPERTY_KEY);
151            if (taskExecutionTimeoutProperty != null && !"".equals(taskExecutionTimeoutProperty)) {
152                Long taskExecutionTimeout = Long.valueOf(taskExecutionTimeoutProperty);
153                configuration.setTaskExecutionTimeout(taskExecutionTimeout);
154            }
155            String taskQueueTimeoutProperty = Framework.getProperty(TASK_QUEUE_TIMEOUT_PROPERTY_KEY);
156            if (taskQueueTimeoutProperty != null && !"".equals(taskQueueTimeoutProperty)) {
157                Long taskQueueTimeout = Long.valueOf(taskQueueTimeoutProperty);
158                configuration.setTaskQueueTimeout(taskQueueTimeout);
159            }
160            String templateProfileDir = Framework.getProperty(TEMPLATE_PROFILE_DIR_PROPERTY_KEY);
161            if (templateProfileDir != null && !"".equals(templateProfileDir)) {
162                File templateDirectory = new File(templateProfileDir);
163                if (!templateDirectory.exists()) {
164                    try {
165                        FileUtils.forceMkdir(templateDirectory);
166                    } catch (IOException e) {
167                        throw new RuntimeException("I/O Error: could not create JOD templateDirectory");
168                    }
169                }
170                configuration.setTemplateProfileDir(templateDirectory);
171            }
172
173            // Descriptor configuration
174            String pipeNamesProperty = Framework.getProperty(OFFICE_PIPES_PROPERTY_KEY);
175            String[] pipeNames = null;
176            if (pipeNamesProperty != null) {
177                String[] unvalidatedPipeNames = pipeNamesProperty.split(",\\s*");
178                ArrayList<String> validatedPipeNames = new ArrayList<>();
179                // Basic validation to avoid empty strings
180                for (int i = 0; i < unvalidatedPipeNames.length; i++) {
181                    String tmpPipeName = unvalidatedPipeNames[i].trim();
182                    if (tmpPipeName.length() > 0) {
183                        validatedPipeNames.add(tmpPipeName);
184                    }
185                }
186                pipeNames = validatedPipeNames.toArray(new String[0]);
187            } else {
188                pipeNames = descriptor.getPipeNames();
189            }
190            if (pipeNames != null && pipeNames.length != 0) {
191                configuration.setPipeNames(pipeNames);
192            }
193            String portNumbersProperty = Framework.getProperty(OFFICE_PORTS_PROPERTY_KEY);
194            int[] portNumbers = null;
195            if (portNumbersProperty != null) {
196                String[] portStrings = portNumbersProperty.split(",\\s*");
197                ArrayList<Integer> portList = new ArrayList<>();
198                for (int i = 0; i < portStrings.length; i++) {
199                    try {
200                        portList.add(Integer.parseInt(portStrings[i].trim()));
201                    } catch (NumberFormatException e) {
202                        log.error("Ignoring malformed port number: " + portStrings[i]);
203                    }
204                }
205                portNumbers = ArrayUtils.toPrimitive(portList.toArray(new Integer[0]));
206            } else {
207                portNumbers = descriptor.getPortNumbers();
208            }
209            if (portNumbers != null && portNumbers.length != 0) {
210                configuration.setPortNumbers(portNumbers);
211            }
212            try {
213                officeManager = configuration.buildOfficeManager();
214                officeManager.start();
215                started = true;
216                log.debug("Starting ooo manager.");
217            } catch (IllegalStateException | OfficeException e) {
218                started = false;
219                Throwable t = unwrapException(e);
220                log.warn("OpenOffice was not found, JOD Converter " + "won't be available: " + t.getMessage());
221            }
222        } finally {
223            starting = false;
224        }
225    }
226
227    public Throwable unwrapException(Throwable t) {
228        Throwable cause = t.getCause();
229        return cause == null ? t : unwrapException(cause);
230    }
231
232    @Override
233    public void applicationStarted(ComponentContext context) {
234        log.info("Starting OOo manager");
235        Runnable oooStarter = new Runnable() {
236            @Override
237            public void run() {
238                try {
239                    startOOoManager();
240                } catch (IOException e) {
241                    log.error("Could not start OOoManager.", e);
242                }
243            }
244        };
245        Thread oooStarterThread = new Thread(oooStarter);
246        oooStarterThread.setDaemon(true);
247        oooStarterThread.start();
248        log.info("Started OOo Manager");
249    }
250
251    @Override
252    public boolean isOOoManagerStarted() {
253        if (shutingdown) {
254            return false;
255        }
256        if (!starting) {
257            return started;
258        }
259
260        // wait a little bit
261        // while we are starting Ooo
262        for (int i = 0; i < 200; i++) {
263            if (starting) {
264                try {
265                    Thread.sleep(300);
266                } catch (InterruptedException e) {
267                    // NOP
268                }
269            }
270            if (!starting) {
271                return started;
272            }
273        }
274
275        log.error("Timeout on waiting for officeManager to start");
276
277        return started;
278    }
279
280    public OfficeManager getOfficeManager() {
281        return officeManager;
282    }
283}