1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.officeimporter.script

File OfficeImporterScriptService.java

 

Coverage histogram

../../../../img/srcFileCovDistChart3.png
80% of files have more coverage

Code metrics

24
79
14
1
473
221
34
0.43
5.64
14
2.43

Classes

Class Line # Actions
OfficeImporterScriptService 62 79 0% 34 87
0.2564102725.6%
 

Contributing tests

This file is covered by 2 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.officeimporter.script;
21   
22    import java.io.InputStream;
23    import java.util.Map;
24   
25    import javax.inject.Inject;
26    import javax.inject.Named;
27    import javax.inject.Singleton;
28   
29    import org.artofsolving.jodconverter.document.DocumentFamily;
30    import org.artofsolving.jodconverter.document.DocumentFormat;
31    import org.slf4j.Logger;
32    import org.xwiki.bridge.DocumentAccessBridge;
33    import org.xwiki.component.annotation.Component;
34    import org.xwiki.context.Execution;
35    import org.xwiki.model.reference.AttachmentReference;
36    import org.xwiki.model.reference.DocumentReference;
37    import org.xwiki.model.reference.DocumentReferenceResolver;
38    import org.xwiki.officeimporter.OfficeImporterException;
39    import org.xwiki.officeimporter.builder.PresentationBuilder;
40    import org.xwiki.officeimporter.builder.XDOMOfficeDocumentBuilder;
41    import org.xwiki.officeimporter.builder.XHTMLOfficeDocumentBuilder;
42    import org.xwiki.officeimporter.converter.OfficeConverter;
43    import org.xwiki.officeimporter.document.XDOMOfficeDocument;
44    import org.xwiki.officeimporter.document.XHTMLOfficeDocument;
45    import org.xwiki.officeimporter.server.OfficeServer;
46    import org.xwiki.officeimporter.server.OfficeServer.ServerState;
47    import org.xwiki.officeimporter.server.OfficeServerConfiguration;
48    import org.xwiki.officeimporter.server.OfficeServerException;
49    import org.xwiki.officeimporter.splitter.TargetDocumentDescriptor;
50    import org.xwiki.officeimporter.splitter.XDOMOfficeDocumentSplitter;
51    import org.xwiki.script.service.ScriptService;
52   
53    /**
54    * Exposes the office importer APIs to server-side scripts.
55    *
56    * @version $Id: 1e2046b4bc415f95c2c1fc35d4a259fbb2883cdb $
57    * @since 4.1M1
58    */
59    @Component
60    @Named("officeimporter")
61    @Singleton
 
62    public class OfficeImporterScriptService implements ScriptService
63    {
64    /**
65    * The key used to place any error messages while importing office documents.
66    */
67    public static final String OFFICE_IMPORTER_ERROR = "OFFICE_IMPORTER_ERROR";
68   
69    /**
70    * The object used to log messages.
71    */
72    @Inject
73    private Logger logger;
74   
75    /**
76    * The {@link Execution} component.
77    */
78    @Inject
79    private Execution execution;
80   
81    /**
82    * The {@link DocumentAccessBridge} component.
83    */
84    @Inject
85    private DocumentAccessBridge docBridge;
86   
87    /**
88    * Used for converting string document names to objects.
89    */
90    @Inject
91    @Named("currentmixed")
92    private DocumentReferenceResolver<String> currentMixedDocumentReferenceResolver;
93   
94    /**
95    * Used to query office server status.
96    */
97    @Inject
98    private OfficeServer officeServer;
99   
100    /**
101    * Used to query the office server configuration.
102    */
103    @Inject
104    private OfficeServerConfiguration officeServerConfiguration;
105   
106    /**
107    * Used for building {@link XHTMLOfficeDocument} instances from office documents.
108    */
109    @Inject
110    private XHTMLOfficeDocumentBuilder xhtmlBuilder;
111   
112    /**
113    * Used for building {@link XDOMOfficeDocument} instances from office documents.
114    */
115    @Inject
116    private XDOMOfficeDocumentBuilder xdomBuilder;
117   
118    /**
119    * Used for building {@link XDOMOfficeDocument} instances from office presentations.
120    */
121    @Inject
122    private PresentationBuilder presentationBuilder;
123   
124    /**
125    * Used to split {@link XDOMOfficeDocument} documents.
126    */
127    @Inject
128    private XDOMOfficeDocumentSplitter xdomSplitter;
129   
130    /**
131    * Imports the given office document into an {@link XHTMLOfficeDocument}.
132    *
133    * @param officeFileStream binary data stream corresponding to input office document
134    * @param officeFileName name of the input office document, this argument is mainly used for determining input
135    * document format where necessary
136    * @param targetDocumentReference the document the import process is carried out relative to; this argument affects
137    * the attachment URLs generated during the import process where all references to attachments will be
138    * calculated assuming that the attachments are contained on the specified target document
139    * @param filterStyles whether to filter styling information associated with the office document's content or not
140    * @return {@link XHTMLOfficeDocument} containing xhtml result of the import operation or null if an error occurs
141    * @since 4.3M1
142    */
 
143  0 toggle public XHTMLOfficeDocument officeToXHTML(InputStream officeFileStream, String officeFileName,
144    DocumentReference targetDocumentReference, boolean filterStyles)
145    {
146  0 try {
147  0 assertConnected();
148  0 return this.xhtmlBuilder.build(officeFileStream, officeFileName, targetDocumentReference, filterStyles);
149    } catch (Exception ex) {
150  0 setErrorMessage(ex.getMessage());
151  0 logger.error(ex.getMessage(), ex);
152    }
153  0 return null;
154    }
155   
156    /**
157    * Imports the given office document into an {@link XHTMLOfficeDocument}.
158    *
159    * @param officeFileStream binary data stream corresponding to input office document
160    * @param officeFileName name of the input office document, this argument is mainly used for determining input
161    * document format where necessary
162    * @param referenceDocument reference wiki document w.r.t which import process is carried out; this argument affects
163    * the attachment URLs generated during the import process where all references to attachments will be
164    * calculated assuming that the attachments are contained on the reference document
165    * @param filterStyles whether to filter styling information associated with the office document's content or not
166    * @return {@link XHTMLOfficeDocument} containing xhtml result of the import operation or null if an error occurs
167    * @since 2.2M1
168    * @deprecated use {@link #officeToXHTML(InputStream, String, DocumentReference, boolean)} instead
169    */
 
170  0 toggle public XHTMLOfficeDocument officeToXHTML(InputStream officeFileStream, String officeFileName,
171    String referenceDocument, boolean filterStyles)
172    {
173  0 return officeToXHTML(officeFileStream, officeFileName,
174    this.currentMixedDocumentReferenceResolver.resolve(referenceDocument), filterStyles);
175    }
176   
177    /**
178    * Imports the given {@link XHTMLOfficeDocument} into an {@link XDOMOfficeDocument}.
179    *
180    * @param xhtmlOfficeDocument {@link XHTMLOfficeDocument} to be imported
181    * @return {@link XDOMOfficeDocument} containing {@link org.xwiki.rendering.block.XDOM} result of the import
182    * operation or null if an error occurs
183    */
 
184  0 toggle public XDOMOfficeDocument xhtmlToXDOM(XHTMLOfficeDocument xhtmlOfficeDocument)
185    {
186  0 try {
187  0 return this.xdomBuilder.build(xhtmlOfficeDocument);
188    } catch (OfficeImporterException ex) {
189  0 setErrorMessage(ex.getMessage());
190  0 logger.error(ex.getMessage(), ex);
191    }
192  0 return null;
193    }
194   
195    /**
196    * Imports the given office document into an {@link XDOMOfficeDocument}.
197    *
198    * @param officeFileStream binary data stream corresponding to input office document
199    * @param officeFileName name of the input office document, this argument is mainly is used for determining input
200    * document format where necessary
201    * @param targetDocumentReference the document the import process is carried out relative to; this argument affects
202    * the attachment URLs generated during the import process where all references to attachments will be
203    * calculated assuming that the attachments are contained on the specified target document
204    * @param filterStyles whether to filter styling information associated with the office document's content or not
205    * @return {@link XDOMOfficeDocument} containing {@link org.xwiki.rendering.block.XDOM} result of the import
206    * operation or null if an error occurs
207    * @since 4.3M1
208    */
 
209  0 toggle public XDOMOfficeDocument officeToXDOM(InputStream officeFileStream, String officeFileName,
210    DocumentReference targetDocumentReference, boolean filterStyles)
211    {
212  0 try {
213  0 assertConnected();
214  0 if (isPresentation(officeFileName)) {
215  0 return this.presentationBuilder.build(officeFileStream, officeFileName, targetDocumentReference);
216    } else {
217  0 return this.xdomBuilder.build(officeFileStream, officeFileName, targetDocumentReference, filterStyles);
218    }
219    } catch (Exception ex) {
220  0 setErrorMessage(ex.getMessage());
221  0 logger.error(ex.getMessage(), ex);
222    }
223  0 return null;
224    }
225   
226    /**
227    * Imports the given office document into an {@link XDOMOfficeDocument}.
228    *
229    * @param officeFileStream binary data stream corresponding to input office document
230    * @param officeFileName name of the input office document, this argument is mainly is used for determining input
231    * document format where necessary
232    * @param referenceDocument reference wiki document w.r.t which import process is carried out; this srgument affects
233    * the attachment URLs generated during the import process where all references to attachments will be
234    * calculated assuming that the attachments are contained on the reference document
235    * @param filterStyles whether to filter styling information associated with the office document's content or not
236    * @return {@link XDOMOfficeDocument} containing {@link org.xwiki.rendering.block.XDOM} result of the import
237    * operation or null if an error occurs
238    * @deprecated use {@link #officeToXDOM(InputStream, String, DocumentReference, boolean)} instead
239    */
 
240  0 toggle public XDOMOfficeDocument officeToXDOM(InputStream officeFileStream, String officeFileName,
241    String referenceDocument, boolean filterStyles)
242    {
243  0 return officeToXDOM(officeFileStream, officeFileName,
244    this.currentMixedDocumentReferenceResolver.resolve(referenceDocument), filterStyles);
245    }
246   
247    /**
248    * Splits the given {@link XDOMOfficeDocument} into multiple {@link XDOMOfficeDocument} instances according to the
249    * specified criterion. This method is useful when a single office document has to be imported and split into
250    * multiple wiki pages. An auto generated TOC structure will be returned associated to <b>rootDocumentName</b>
251    * {@link org.xwiki.officeimporter.splitter.TargetDocumentDescriptor} entry.
252    *
253    * @param xdomDocument {@link XDOMOfficeDocument} to be split
254    * @param headingLevels heading levels defining the split points on the original document
255    * @param namingCriterionHint hint indicating the child pages naming criterion
256    * @param rootDocumentReference the reference of the root document w.r.t which splitting will occur; in the results
257    * set the entry corresponding to the <b>root document</b> {@link TargetDocumentDescriptor} will hold an
258    * auto-generated TOC structure
259    * @return a map holding {@link XDOMOfficeDocument} fragments against corresponding {@link TargetDocumentDescriptor}
260    * instances or null if an error occurs
261    * @since 4.3M1
262    */
 
263  0 toggle public Map<TargetDocumentDescriptor, XDOMOfficeDocument> split(XDOMOfficeDocument xdomDocument,
264    String[] headingLevels, String namingCriterionHint, DocumentReference rootDocumentReference)
265    {
266  0 int[] splitLevels = new int[headingLevels.length];
267  0 for (int i = 0; i < headingLevels.length; i++) {
268  0 splitLevels[i] = Integer.parseInt(headingLevels[i]);
269    }
270  0 try {
271  0 return this.xdomSplitter.split(xdomDocument, splitLevels, namingCriterionHint, rootDocumentReference);
272    } catch (OfficeImporterException ex) {
273  0 setErrorMessage(ex.getMessage());
274  0 logger.error(ex.getMessage(), ex);
275    }
276  0 return null;
277    }
278   
279    /**
280    * Splits the given {@link XDOMOfficeDocument} into multiple {@link XDOMOfficeDocument} instances according to the
281    * specified criterion. This method is useful when a single office document has to be imported and split into
282    * multiple wiki pages. An auto generated TOC structure will be returned associated to <b>rootDocumentName</b>
283    * {@link org.xwiki.officeimporter.splitter.TargetDocumentDescriptor} entry.
284    *
285    * @param xdomDocument {@link XDOMOfficeDocument} to be split
286    * @param headingLevels heading levels defining the split points on the original document
287    * @param namingCriterionHint hint indicating the child pages naming criterion
288    * @param rootDocumentName name of the root document w.r.t which splitting will occur; in the results set the entry
289    * corresponding to <b>rootDocumentName</b> {@link TargetDocumentDescriptor} will hold an auto-generated
290    * TOC structure
291    * @return a map holding {@link XDOMOfficeDocument} fragments against corresponding {@link TargetDocumentDescriptor}
292    * instances or null if an error occurs
293    * @deprecated use {@link #split(XDOMOfficeDocument, String[], String, DocumentReference)} instead
294    */
 
295  0 toggle public Map<TargetDocumentDescriptor, XDOMOfficeDocument> split(XDOMOfficeDocument xdomDocument,
296    String[] headingLevels, String namingCriterionHint, String rootDocumentName)
297    {
298  0 return split(xdomDocument, headingLevels, namingCriterionHint,
299    this.currentMixedDocumentReferenceResolver.resolve(rootDocumentName));
300    }
301   
302    /**
303    * Attempts to save the given {@link XDOMOfficeDocument} into the target wiki page specified by arguments.
304    *
305    * @param doc {@link XDOMOfficeDocument} to be saved
306    * @param documentReference the reference of the target wiki page
307    * @param syntaxId syntax of the target wiki page
308    * @param parentReference the reference of the parent wiki page or {@code null}
309    * @param title title of the target wiki page or {@code null}
310    * @param append whether to append content if the target wiki page exists
311    * @return true if the operation completes successfully, false otherwise
312    * @since 4.3M1
313    */
 
314  2 toggle public boolean save(XDOMOfficeDocument doc, DocumentReference documentReference, String syntaxId,
315    DocumentReference parentReference, String title, boolean append)
316    {
317  2 try {
318    // First check if the user has edit rights on the target document.
319  2 if (!this.docBridge.isDocumentEditable(documentReference)) {
320  0 String message = "You do not have edit rights on [%s] document.";
321  0 throw new OfficeImporterException(String.format(message, documentReference));
322    }
323   
324    // Save.
325  2 if (this.docBridge.exists(documentReference) && append) {
326    // Check whether existing document's syntax is same as target syntax.
327  1 String currentSyntaxId = this.docBridge.getDocument(documentReference).getSyntax().toIdString();
328  1 if (!currentSyntaxId.equals(syntaxId)) {
329  0 String message =
330    "The target page [%s] exists but its syntax [%s] is different from the specified syntax [%s]";
331  0 throw new OfficeImporterException(String.format(message, documentReference, currentSyntaxId,
332    syntaxId));
333    }
334   
335    // Append the content.
336  1 String currentContent = this.docBridge.getDocumentContent(documentReference, null);
337  1 String newContent = currentContent + "\n" + doc.getContentAsString(syntaxId);
338  1 this.docBridge.setDocumentContent(documentReference, newContent, "Updated by office importer.", false);
339    } else {
340  1 this.docBridge.setDocumentSyntaxId(documentReference, syntaxId);
341  1 this.docBridge.setDocumentContent(documentReference, doc.getContentAsString(syntaxId),
342    "Created by office importer.", false);
343   
344    // Set parent if provided.
345  1 if (null != parentReference) {
346  1 this.docBridge.setDocumentParentReference(documentReference, parentReference);
347    }
348   
349    // If no title is specified, try to extract one.
350  1 String docTitle = (null == title) ? doc.getTitle() : title;
351   
352    // Set title if applicable.
353  1 if (null != docTitle) {
354  1 this.docBridge.setDocumentTitle(documentReference, docTitle);
355    }
356    }
357   
358    // Finally attach all the artifacts into target document.
359  2 attachArtifacts(doc.getArtifacts(), documentReference);
360   
361  2 return true;
362    } catch (OfficeImporterException ex) {
363  0 setErrorMessage(ex.getMessage());
364  0 logger.error(ex.getMessage(), ex);
365    } catch (Exception ex) {
366  0 String message = "Error while saving document [%s].";
367  0 message = String.format(message, documentReference);
368  0 setErrorMessage(message);
369  0 logger.error(message, ex);
370    }
371  0 return false;
372    }
373   
374    /**
375    * Attempts to save the given {@link XDOMOfficeDocument} into the target wiki page specified by arguments.
376    *
377    * @param doc {@link XDOMOfficeDocument} to be saved
378    * @param target name of the target wiki page
379    * @param syntaxId syntax of the target wiki page
380    * @param parent name of the parent wiki page or null
381    * @param title title of the target wiki page or null
382    * @param append whether to append content if the target wiki page exists
383    * @return true if the operation completes successfully, false otherwise
384    * @deprecated use {@link #save(XDOMOfficeDocument, DocumentReference, String, DocumentReference, String, boolean)}
385    * instead
386    */
 
387  0 toggle public boolean save(XDOMOfficeDocument doc, String target, String syntaxId, String parent, String title,
388    boolean append)
389    {
390  0 return save(doc, this.currentMixedDocumentReferenceResolver.resolve(target), syntaxId,
391    this.currentMixedDocumentReferenceResolver.resolve(parent), title, append);
392    }
393   
394    /**
395    * @return an error message set inside current execution (during import process) or null
396    */
 
397  0 toggle public String getErrorMessage()
398    {
399  0 return (String) this.execution.getContext().getProperty(OFFICE_IMPORTER_ERROR);
400    }
401   
402    /**
403    * Utility method for setting an error message inside current execution.
404    *
405    * @param message error message
406    */
 
407  0 toggle private void setErrorMessage(String message)
408    {
409  0 this.execution.getContext().setProperty(OFFICE_IMPORTER_ERROR, message);
410    }
411   
412    /**
413    * Checks if the connection to the office server has been established. If the office server has been configured to
414    * start automatically then we make an attempt to start it (usually this means that the office server failed to
415    * start when XE started and so we try one more time to connect).
416    *
417    * @throws OfficeImporterException if the connection to the office server is not established
418    * @throws OfficeServerException if the attempt to start the office server failed
419    */
 
420  0 toggle private void assertConnected() throws OfficeImporterException, OfficeServerException
421    {
422  0 boolean connected = this.officeServer.getState().equals(ServerState.CONNECTED);
423  0 if (!connected) {
424    // Check if the office server was configured to start automatically.
425  0 if (this.officeServerConfiguration.isAutoStart()) {
426    // The office server probably failed to start automatically when XE started. Try one more time to
427    // connect.
428  0 this.officeServer.start();
429  0 connected = this.officeServer.getState().equals(ServerState.CONNECTED);
430    }
431  0 if (!connected) {
432  0 throw new OfficeImporterException("Office server unavailable.");
433    }
434    }
435    }
436   
437    /**
438    * Utility method for checking if a file name corresponds to an office presentation.
439    *
440    * @param officeFileName office file name
441    * @return true if the file name / extension represents an office presentation format
442    */
 
443  0 toggle private boolean isPresentation(String officeFileName)
444    {
445  0 String extension = officeFileName.substring(officeFileName.lastIndexOf('.') + 1);
446  0 OfficeConverter officeConverter = officeServer.getConverter();
447  0 if (officeConverter != null) {
448  0 DocumentFormat format = officeConverter.getFormatRegistry().getFormatByExtension(extension);
449  0 return format != null && format.getInputFamily() == DocumentFamily.PRESENTATION;
450    }
451  0 return false;
452    }
453   
454    /**
455    * Utility method for attaching artifacts into a wiki page.
456    *
457    * @param artifacts map of artifact content against their names
458    * @param targetDocumentReference target wiki page into which artifacts are to be attached
459    */
 
460  2 toggle private void attachArtifacts(Map<String, byte[]> artifacts, DocumentReference targetDocumentReference)
461    {
462  2 for (Map.Entry<String, byte[]> artifact : artifacts.entrySet()) {
463  1 AttachmentReference attachmentReference =
464    new AttachmentReference(artifact.getKey(), targetDocumentReference);
465  1 try {
466  1 this.docBridge.setAttachmentContent(attachmentReference, artifact.getValue());
467    } catch (Exception ex) {
468    // Log the error and skip the artifact.
469  0 logger.error("Error while attaching artifact.", ex);
470    }
471    }
472    }
473    }