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