001/*
002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others.
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 *     Thomas Roger
016 */
017package org.nuxeo.ecm.restapi.server.jaxrs.adapters;
018
019import java.io.Serializable;
020import java.net.URI;
021import java.net.URISyntaxException;
022import java.util.HashMap;
023import java.util.Map;
024
025import javax.ws.rs.FormParam;
026import javax.ws.rs.GET;
027import javax.ws.rs.POST;
028import javax.ws.rs.Produces;
029import javax.ws.rs.QueryParam;
030import javax.ws.rs.core.Context;
031import javax.ws.rs.core.MediaType;
032import javax.ws.rs.core.MultivaluedMap;
033import javax.ws.rs.core.Response;
034import javax.ws.rs.core.UriInfo;
035
036import org.apache.commons.lang.StringUtils;
037import org.nuxeo.ecm.core.api.Blob;
038import org.nuxeo.ecm.core.api.DocumentModel;
039import org.nuxeo.ecm.core.api.NuxeoException;
040import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
041import org.nuxeo.ecm.core.api.blobholder.SimpleBlobHolder;
042import org.nuxeo.ecm.core.convert.api.ConversionService;
043import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry;
044import org.nuxeo.ecm.restapi.jaxrs.io.conversion.ConversionScheduled;
045import org.nuxeo.ecm.restapi.server.jaxrs.blob.BlobObject;
046import org.nuxeo.ecm.webengine.model.WebAdapter;
047import org.nuxeo.ecm.webengine.model.exceptions.IllegalParameterException;
048import org.nuxeo.ecm.webengine.model.exceptions.WebResourceNotFoundException;
049import org.nuxeo.ecm.webengine.model.impl.DefaultAdapter;
050import org.nuxeo.runtime.api.Framework;
051import org.nuxeo.runtime.transaction.TransactionHelper;
052
053/**
054 * Adapter allowing to convert a Blob using a named converter or a destination mime type.
055 *
056 * @since 7.3
057 */
058@WebAdapter(name = ConvertAdapter.NAME, type = "convertAdapter")
059@Produces({ "application/json+nxentity", MediaType.APPLICATION_JSON })
060public class ConvertAdapter extends DefaultAdapter {
061
062    public static final String NAME = "convert";
063
064    @GET
065    public Blob convert(@QueryParam("converter") String converter, @QueryParam("type") String type,
066            @QueryParam("format") String format, @Context UriInfo uriInfo) {
067        BlobHolder bh = getBlobHolderToConvert();
068
069        boolean txWasActive = false;
070        try {
071            if (TransactionHelper.isTransactionActive()) {
072                txWasActive = true;
073                TransactionHelper.commitOrRollbackTransaction();
074            }
075
076            if (StringUtils.isNotBlank(converter)) {
077                return convertWithConverter(bh, converter, uriInfo);
078            } else if (StringUtils.isNotBlank(type)) {
079                return convertWithMimeType(bh, type, uriInfo);
080            } else if (StringUtils.isNotBlank(format)) {
081                return convertWithFormat(bh, format, uriInfo);
082            } else {
083                throw new IllegalParameterException("No converter, type or format parameter specified");
084            }
085        } finally {
086            if (txWasActive && !TransactionHelper.isTransactionActiveOrMarkedRollback()) {
087                TransactionHelper.startTransaction();
088            }
089        }
090    }
091
092    protected BlobHolder getBlobHolderToConvert() {
093        Blob blob = getTarget().getAdapter(Blob.class);
094        BlobHolder bh = null;
095        if (blob == null) {
096            DocumentModel doc = getTarget().getAdapter(DocumentModel.class);
097            if (doc != null) {
098                bh = doc.getAdapter(BlobHolder.class);
099                if (bh != null) {
100                    blob = bh.getBlob();
101                }
102            }
103        }
104        if (blob == null) {
105            throw new IllegalParameterException("No Blob found");
106        }
107
108        if (getTarget().isInstanceOf("blob")) {
109            bh = ((BlobObject) getTarget()).getBlobHolder();
110        }
111
112        if (bh == null) {
113            bh = new SimpleBlobHolder(blob);
114        }
115        return bh;
116    }
117
118    protected Blob convertWithConverter(BlobHolder bh, String converter, UriInfo uriInfo) {
119        ConversionService conversionService = Framework.getService(ConversionService.class);
120        if (!conversionService.isConverterAvailable(converter).isAvailable()) {
121            throw new IllegalParameterException(String.format("The '%s' converter is not available", converter));
122        }
123        Map<String, Serializable> parameters = computeConversionParameters(uriInfo);
124        BlobHolder blobHolder = conversionService.convert(converter, bh, parameters);
125        Blob conversionBlob = blobHolder.getBlob();
126        if (conversionBlob == null) {
127            throw new WebResourceNotFoundException(String.format("No converted Blob using '%s' converter", converter));
128        }
129        return conversionBlob;
130    }
131
132    protected Map<String, Serializable> computeConversionParameters(UriInfo uriInfo) {
133        MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
134        Map<String, Serializable> parameters = new HashMap<>();
135        for (String parameterKey : queryParams.keySet()) {
136            parameters.put(parameterKey, queryParams.getFirst(parameterKey));
137        }
138        return parameters;
139    }
140
141    protected Blob convertWithMimeType(BlobHolder bh, String mimeType, UriInfo uriInfo) {
142        Map<String, Serializable> parameters = computeConversionParameters(uriInfo);
143        ConversionService conversionService = Framework.getService(ConversionService.class);
144        BlobHolder blobHolder = conversionService.convertToMimeType(mimeType, bh, parameters);
145        Blob conversionBlob = blobHolder.getBlob();
146        if (conversionBlob == null) {
147            throw new WebResourceNotFoundException(String.format("No converted Blob for '%s' mime type", mimeType));
148        }
149        return conversionBlob;
150    }
151
152    protected Blob convertWithFormat(BlobHolder bh, String format, UriInfo uriInfo) {
153        MimetypeRegistry mimetypeRegistry = Framework.getService(MimetypeRegistry.class);
154        String mimeType = mimetypeRegistry.getMimetypeFromExtension(format);
155        return convertWithMimeType(bh, mimeType, uriInfo);
156    }
157
158    @POST
159    public Object convert(@FormParam("converter") String converter, @FormParam("type") String type,
160            @FormParam("format") String format, @FormParam("async") boolean async, @Context UriInfo uriInfo) {
161        if (!async) {
162            return convert(converter, type, format, uriInfo);
163        }
164
165        String conversionId;
166        BlobHolder bh = getBlobHolderToConvert();
167        Map<String, Serializable> parameters = computeConversionParameters(uriInfo);
168        ConversionService conversionService = Framework.getService(ConversionService.class);
169        if (StringUtils.isNotBlank(converter)) {
170            conversionId = conversionService.scheduleConversion(converter, bh, parameters);
171        } else if (StringUtils.isNotBlank(type)) {
172            conversionId = conversionService.scheduleConversionToMimeType(type, bh, parameters);
173        } else if (StringUtils.isNotBlank(format)) {
174            MimetypeRegistry mimetypeRegistry = Framework.getService(MimetypeRegistry.class);
175            String mimeType = mimetypeRegistry.getMimetypeFromExtension(format);
176            conversionId = conversionService.scheduleConversionToMimeType(mimeType, bh, parameters);
177        } else {
178            throw new IllegalParameterException("No converter, type or format parameter specified");
179        }
180
181        String serverURL = ctx.getServerURL().toString();
182        if (serverURL.endsWith("/")) {
183            serverURL = serverURL.substring(0, serverURL.length() - 1);
184        }
185        String pollingURL = String.format("%s%s/conversions/%s/poll", serverURL, ctx.getModulePath(), conversionId);
186        ConversionScheduled conversionScheduled = new ConversionScheduled(conversionId, pollingURL);
187        try {
188            return Response.status(Response.Status.ACCEPTED)
189                           .location(new URI(pollingURL))
190                           .entity(conversionScheduled)
191                           .build();
192        } catch (URISyntaxException e) {
193            throw new NuxeoException(e);
194        }
195    }
196}