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