001/*
002 * (C) Copyright 2006-2016 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 *     Florent Bonnet <florent.bonnet@nuxeo.com>
019 *     Estelle Giuly <egiuly@nuxeo.com>
020 *
021 * $Id: JOOoConvertPluginImpl.java 18651 2007-05-13 20:28:53Z sfermigier $
022 */
023
024package org.nuxeo.ecm.webapp.action;
025
026import static org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry.PDF_MIMETYPE;
027import static org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry.PDF_EXTENSION;
028
029import java.io.IOException;
030import java.net.URI;
031import java.util.Collections;
032import java.util.HashMap;
033import java.util.Iterator;
034import java.util.Map;
035
036import javax.faces.context.FacesContext;
037
038import org.apache.commons.lang3.StringUtils;
039import org.apache.commons.logging.Log;
040import org.apache.commons.logging.LogFactory;
041import org.jboss.seam.ScopeType;
042import org.jboss.seam.annotations.In;
043import org.jboss.seam.annotations.Name;
044import org.jboss.seam.annotations.Scope;
045import org.jboss.seam.annotations.remoting.WebRemote;
046import org.jboss.seam.annotations.web.RequestParameter;
047import org.nuxeo.ecm.core.api.Blob;
048import org.nuxeo.ecm.core.api.CoreSession;
049import org.nuxeo.ecm.core.api.DocumentModel;
050import org.nuxeo.ecm.core.api.IdRef;
051import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
052import org.nuxeo.ecm.core.api.blobholder.DocumentBlobHolder;
053import org.nuxeo.ecm.core.blob.BlobManager;
054import org.nuxeo.ecm.core.blob.BlobManager.UsageHint;
055import org.nuxeo.ecm.core.convert.api.ConversionService;
056import org.nuxeo.ecm.core.convert.api.ConverterCheckResult;
057import org.nuxeo.ecm.core.convert.api.ConverterNotRegistered;
058import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry;
059import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
060import org.nuxeo.ecm.platform.ui.web.cache.ThreadSafeCacheHolder;
061import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils;
062import org.nuxeo.runtime.api.Framework;
063
064@Name("conversionActions")
065@Scope(ScopeType.EVENT)
066public class ConversionActionBean implements ConversionAction {
067
068    private static final Log log = LogFactory.getLog(ConversionActionBean.class);
069
070    protected Map<String, ConverterCheckResult> pdfConverterForTypes;
071
072    @In(create = true, required = false)
073    CoreSession documentManager;
074
075    @In(create = true)
076    NavigationContext navigationContext;
077
078    @RequestParameter
079    private String docRef;
080
081    @RequestParameter
082    private String fileFieldFullName;
083
084    @RequestParameter
085    private String filename;
086
087    protected String pdfConverterName;
088
089    protected static final ThreadSafeCacheHolder<Boolean> exportableToPDFCache = new ThreadSafeCacheHolder<>(20);
090
091    public String display() {
092        return "view_file";
093    }
094
095    private DocumentModel getDocument() {
096        if (docRef == null) {
097            return navigationContext.getCurrentDocument();
098        } else {
099            return documentManager.getDocument(new IdRef(docRef));
100        }
101    }
102
103    private String getMimetypeFromDocument(String propertyName) {
104        Blob blob = (Blob) getDocument().getPropertyValue(propertyName);
105        return blob.getMimeType();
106    }
107
108    @Override
109    public void reCheckConverterAvailability() {
110        pdfConverterForTypes.clear();
111    }
112
113    public boolean isExportableToPDF(BlobHolder bh) {
114        if (bh == null) {
115            return false;
116        }
117        Blob blob = bh.getBlob();
118        return blob != null && isExportableToPDF(blob);
119    }
120
121    @Override
122    public boolean isExportableToPDF(Blob blob) {
123        if (blob == null) {
124            return false;
125        }
126        // check if there's a conversion available
127        if (getPDFConversionURL(blob) != null) {
128            return true;
129        }
130        String mimeType = blob.getMimeType();
131        return isMimeTypeExportableToPDF(mimeType);
132    }
133
134    protected String getPDFConversionURL(Blob blob) {
135        BlobManager blobManager = Framework.getService(BlobManager.class);
136        try {
137            URI uri = blobManager.getAvailableConversions(blob, UsageHint.DOWNLOAD).get(PDF_MIMETYPE);
138            if (uri != null) {
139                return uri.toString();
140            }
141        } catch (IOException e) {
142            log.error("Failed to retrieve available conversions", e);
143        }
144        return null;
145    }
146
147    protected boolean isMimeTypeExportableToPDF(String mimeType) {
148        // Don't bother searching for NO MIME type.
149        if (mimeType == null) {
150            return false;
151        }
152
153        // Initialize the converter check result map.
154        if (pdfConverterForTypes == null) {
155            pdfConverterForTypes = new HashMap<>();
156        }
157
158        // Check if there is any saved ConverterCheckResult for the desired
159        // MIME type.
160        if (pdfConverterForTypes.containsKey(mimeType)) {
161            return pdfConverterForTypes.get(mimeType).isAvailable();
162        }
163
164        try {
165            ConverterCheckResult pdfConverterAvailability;
166            ConversionService conversionService = Framework.getService(ConversionService.class);
167            Iterator<String> converterNames = conversionService.getConverterNames(mimeType, PDF_MIMETYPE).iterator();
168            while (converterNames.hasNext()) {
169                pdfConverterName = converterNames.next();
170                pdfConverterAvailability = conversionService.isConverterAvailable(pdfConverterName, true);
171
172                // Save the converter availability for all the mime-types the
173                // converter
174                // supports.
175                for (String supMimeType : pdfConverterAvailability.getSupportedInputMimeTypes()) {
176                    pdfConverterForTypes.put(supMimeType, pdfConverterAvailability);
177                }
178
179                if (pdfConverterAvailability.isAvailable()) {
180                    return true;
181                }
182            }
183        } catch (ConverterNotRegistered e) {
184            log.error("Error while testing PDF converter availability", e);
185        }
186        return false;
187    }
188
189    @Override
190    @WebRemote
191    public boolean isFileExportableToPDF(String fieldName) {
192        DocumentModel doc = getDocument();
193        Boolean cacheResult = exportableToPDFCache.getFromCache(doc, fieldName);
194        boolean isSupported;
195        if (cacheResult == null) {
196            String mimetype = getMimetypeFromDocument(fieldName);
197            isSupported = isMimeTypeExportableToPDF(mimetype);
198            exportableToPDFCache.addToCache(doc, fieldName, isSupported);
199        } else {
200            isSupported = cacheResult;
201        }
202        return isSupported;
203    }
204
205    public String generatePdfFileFromBlobHolder(DocumentModel doc, BlobHolder bh) throws IOException {
206        // redirect to the conversion URL when available
207        Blob blob = bh.getBlob();
208        String url = getPDFConversionURL(blob);
209        if (url != null) {
210            try {
211                FacesContext.getCurrentInstance().getExternalContext().redirect(url);
212                return null;
213            } catch (IOException e) {
214                //
215            }
216        }
217        if (pdfConverterName == null) {
218            log.error("No PDF converter was found.");
219            return "pdf_generation_error";
220        }
221        Blob result = Framework.getService(ConversionService.class)
222                               .convertToMimeType(MimetypeRegistry.PDF_MIMETYPE, bh, Collections.emptyMap())
223                               .getBlob();
224        if (result == null) {
225            log.error("Transform service didn't return any resulting documents which is not normal.");
226            return "pdf_generation_error";
227        }
228        String xpath;
229        if (bh instanceof DocumentBlobHolder) {
230            xpath = ((DocumentBlobHolder) bh).getXpath();
231        } else {
232            xpath = null;
233        }
234        ComponentUtils.download(doc, xpath, result, result.getFilename(), "pdfConversion");
235        return null;
236    }
237
238    @Override
239    @WebRemote
240    public String generatePdfFile() throws IOException {
241        DocumentModel doc = getDocument();
242        BlobHolder bh = new DocumentBlobHolder(doc, fileFieldFullName);
243        return generatePdfFileFromBlobHolder(doc, bh);
244    }
245
246    /**
247     * @since 7.3
248     */
249    public boolean isPDF(BlobHolder bh) {
250        if (bh == null) {
251            return false;
252        }
253        Blob blob = bh.getBlob();
254        return blob != null && isPDF(blob);
255    }
256
257    /**
258     * @since 7.3
259     */
260    public boolean isPDF(Blob blob) {
261        if (blob == null) {
262            return false;
263        }
264        String mimeType = blob.getMimeType();
265        if (StringUtils.isNotBlank(mimeType) && PDF_MIMETYPE.equals(mimeType)) {
266            return true;
267        } else {
268            String filename = blob.getFilename();
269            if (StringUtils.isNotBlank(filename) && filename.endsWith(PDF_EXTENSION)) {
270                // assume it's a pdf file
271                return true;
272            }
273        }
274        return false;
275    }
276
277    public void initialize() {
278        // NOP
279    }
280
281}