001/*
002 * (C) Copyright 2014 Nuxeo SAS (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.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 *     vpasquier <vpasquier@nuxeo.com>
017 *
018 */
019package org.nuxeo.ecm.automation.server.jaxrs.batch;
020
021import java.io.ByteArrayOutputStream;
022import java.io.IOException;
023import java.io.InputStream;
024import java.net.URLDecoder;
025import java.util.ArrayList;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029
030import javax.mail.MessagingException;
031import javax.servlet.http.HttpServletRequest;
032import javax.ws.rs.GET;
033import javax.ws.rs.POST;
034import javax.ws.rs.Path;
035import javax.ws.rs.PathParam;
036import javax.ws.rs.Produces;
037import javax.ws.rs.core.Context;
038import javax.ws.rs.core.MediaType;
039import javax.ws.rs.core.Response;
040import javax.ws.rs.core.Response.Status;
041
042import org.apache.commons.lang.StringUtils;
043import org.apache.commons.logging.Log;
044import org.apache.commons.logging.LogFactory;
045import org.codehaus.jackson.map.ObjectMapper;
046import org.nuxeo.ecm.automation.OperationContext;
047import org.nuxeo.ecm.automation.jaxrs.io.operations.ExecutionRequest;
048import org.nuxeo.ecm.automation.server.jaxrs.ResponseHelper;
049import org.nuxeo.ecm.core.api.Blob;
050import org.nuxeo.ecm.core.api.CoreSession;
051import org.nuxeo.ecm.core.api.NuxeoException;
052import org.nuxeo.ecm.webengine.WebException;
053import org.nuxeo.ecm.webengine.forms.FormData;
054import org.nuxeo.ecm.webengine.jaxrs.context.RequestCleanupHandler;
055import org.nuxeo.ecm.webengine.jaxrs.context.RequestContext;
056import org.nuxeo.ecm.webengine.jaxrs.session.SessionFactory;
057import org.nuxeo.ecm.webengine.model.WebObject;
058import org.nuxeo.ecm.webengine.model.impl.AbstractResource;
059import org.nuxeo.ecm.webengine.model.impl.ResourceTypeImpl;
060import org.nuxeo.runtime.api.Framework;
061import org.nuxeo.runtime.services.config.ConfigurationService;
062
063/**
064 * Exposes {@link Batch} as a JAX-RS resource
065 *
066 * @deprecated Use {@link org.nuxeo.ecm.restapi.server.jaxrs.BatchUploadObject} instead.
067 * @author Tiry (tdelprat@nuxeo.com)
068 * @author Antoine Taillefer
069 */
070@Deprecated
071@WebObject(type = "batch")
072public class BatchResource extends AbstractResource<ResourceTypeImpl> {
073
074    private static final String REQUEST_BATCH_ID = "batchId";
075
076    private static final String REQUEST_FILE_IDX = "fileIdx";
077
078    protected static final Log log = LogFactory.getLog(BatchResource.class);
079
080    public CoreSession getCoreSession(HttpServletRequest request) {
081        return SessionFactory.getSession(request);
082    }
083
084    protected Response buildFromString(String message) {
085        return Response.ok(message, MediaType.APPLICATION_JSON).header("Content-Length", message.length()).build();
086    }
087
088    protected Response buildHtmlFromString(String message) {
089        message = "<html>" + message + "</html>";
090        return Response.ok(message, MediaType.TEXT_HTML_TYPE).header("Content-Length", message.length()).build();
091    }
092
093    protected Response buildFromMap(Map<String, String> map) throws IOException {
094        return buildFromMap(map, false);
095    }
096
097    protected Response buildFromMap(Map<String, String> map, boolean html) throws IOException {
098        ObjectMapper mapper = new ObjectMapper();
099        ByteArrayOutputStream out = new ByteArrayOutputStream(128);
100        mapper.writeValue(out, map);
101        String result = out.toString("UTF-8");
102        if (html) {
103            // for msie with iframe transport : we need to return html !
104            return buildHtmlFromString(result);
105        } else {
106            return buildFromString(result);
107        }
108    }
109
110    /**
111     * @deprecated since 5.7.2. The timeout is managed by the {@link BatchManager#execute} method.
112     */
113    @Deprecated
114    protected int getUploadWaitTimeout() {
115        String t = Framework.getProperty("org.nuxeo.batch.upload.wait.timeout", "5");
116        return Integer.parseInt(t);
117    }
118
119    /**
120     * @deprecated since 7.4, use {@link BatchUploadObject#upload(HttpServletRequest, String, String)} instead.
121     */
122    @Deprecated
123    @POST
124    @Path("/upload")
125    public Object doPost(@Context HttpServletRequest request) throws IOException {
126
127        boolean useIFrame = false;
128
129        // Parameters are passed as request header
130        // the request body is the stream
131        String fileName = request.getHeader("X-File-Name");
132        String fileSize = request.getHeader("X-File-Size");
133        String batchId = request.getHeader("X-Batch-Id");
134        String mimeType = request.getHeader("X-File-Type");
135        String idx = request.getHeader("X-File-Idx");
136        InputStream is = null;
137
138        // handle multipart case : mainly MSIE with jQueryFileupload
139        String contentType = request.getHeader("Content-Type");
140        if (contentType != null && contentType.contains("multipart")) {
141            useIFrame = true;
142            FormData formData = new FormData(request);
143            if (formData.getString("batchId") != null) {
144                batchId = formData.getString("batchId");
145            }
146            if (formData.getString("fileIdx") != null) {
147                idx = formData.getString("fileIdx");
148            }
149            if (idx == null || "".equals(idx.trim())) {
150                idx = "0";
151            }
152            Blob blob = formData.getFirstBlob();
153            if (blob != null) {
154                is = blob.getStream();
155                fileName = blob.getFilename();
156                mimeType = blob.getMimeType();
157            }
158        } else {
159            fileName = URLDecoder.decode(fileName, "UTF-8");
160            is = request.getInputStream();
161        }
162
163        log.debug("Uploading " + fileName + " (" + fileSize + "b)");
164        BatchManager bm = Framework.getLocalService(BatchManager.class);
165        if (StringUtils.isEmpty(batchId)) {
166            batchId = bm.initBatch();
167        } else if (!bm.hasBatch(batchId)) {
168            if (!Framework.getService(ConfigurationService.class).isBooleanPropertyTrue(
169                    BatchManagerComponent.CLIENT_BATCH_ID_FLAG)) {
170                String errorMsg = String.format(
171                        "Cannot upload a file with a client-side generated batch id, please use new upload API or set configuration property %s to true (not recommended)",
172                        BatchManagerComponent.CLIENT_BATCH_ID_FLAG);
173                return Response.status(Status.INTERNAL_SERVER_ERROR).entity("{\"error\" : \"" + errorMsg + "\"}").build();
174            } else {
175                log.warn(String.format(
176                        "Allowing to initialize upload batch with a client-side generated id since configuration property %s is set to true but this is not recommended, please use new upload API instead",
177                        BatchManagerComponent.CLIENT_BATCH_ID_FLAG));
178            }
179        }
180        bm.addStream(batchId, idx, is, fileName, mimeType);
181
182        Map<String, String> result = new HashMap<String, String>();
183        result.put("batchId", batchId);
184        result.put("uploaded", "true");
185        return buildFromMap(result, useIFrame);
186    }
187
188    @Deprecated
189    @POST
190    @Produces("application/json")
191    @Path("/execute")
192    public Object exec(@Context HttpServletRequest request, ExecutionRequest xreq) {
193        Map<String, Object> params = xreq.getParams();
194        String batchId = (String) params.get(REQUEST_BATCH_ID);
195        String fileIdx = (String) params.get(REQUEST_FILE_IDX);
196        String operationId = (String) params.get("operationId");
197        params.remove(REQUEST_BATCH_ID);
198        params.remove("operationId");
199
200        final BatchManager bm = Framework.getLocalService(BatchManager.class);
201        // register commit hook for cleanup
202        request.setAttribute(REQUEST_BATCH_ID, batchId);
203        RequestContext.getActiveContext(request).addRequestCleanupHandler(new RequestCleanupHandler() {
204            @Override
205            public void cleanup(HttpServletRequest req) {
206                String bid = (String) req.getAttribute(REQUEST_BATCH_ID);
207                bm.clean(bid);
208            }
209
210        });
211
212        try {
213            OperationContext ctx = xreq.createContext(request, getCoreSession(request));
214
215            Object result;
216            if (StringUtils.isEmpty(fileIdx)) {
217                result = bm.execute(batchId, operationId, getCoreSession(request), ctx, params);
218            } else {
219                result = bm.execute(batchId, fileIdx, operationId, getCoreSession(request), ctx, params);
220            }
221            return ResponseHelper.getResponse(result, request);
222        } catch (NuxeoException | MessagingException | IOException e) {
223            log.error("Error while executing automation batch ", e);
224            if (WebException.isSecurityError(e)) {
225                return Response.status(Status.FORBIDDEN).entity("{\"error\" : \"" + e.getMessage() + "\"}").build();
226            } else {
227                return Response.status(Status.INTERNAL_SERVER_ERROR).entity("{\"error\" : \"" + e.getMessage() + "\"}").build();
228            }
229        }
230    }
231
232    /**
233     * @deprecated since 7.4, use {@link BatchUploadObject#getBatchInfo(String)} instead.
234     */
235    @Deprecated
236    @GET
237    @Path("/files/{batchId}")
238    public Object getFilesBatch(@PathParam(REQUEST_BATCH_ID) String batchId) throws IOException {
239        BatchManager bm = Framework.getLocalService(BatchManager.class);
240        List<Blob> blobs = bm.getBlobs(batchId);
241
242        List<Map<String, Object>> result = new ArrayList<>();
243        for (Blob blob : blobs) {
244            Map<String, Object> map = new HashMap<>();
245            map.put("name", blob.getFilename());
246            map.put("size", blob.getLength());
247            result.add(map);
248        }
249
250        ObjectMapper mapper = new ObjectMapper();
251        ByteArrayOutputStream out = new ByteArrayOutputStream(128);
252        mapper.writeValue(out, result);
253        return buildFromString(out.toString("UTF-8"));
254    }
255
256    /**
257     * @deprecated since 7.4, use {@link BatchUploadObject#dropBatch(String)} instead.
258     */
259    @Deprecated
260    @GET
261    @Path("/drop/{batchId}")
262    public Object dropBatch(@PathParam(REQUEST_BATCH_ID) String batchId) throws IOException {
263        BatchManager bm = Framework.getLocalService(BatchManager.class);
264        bm.clean(batchId);
265
266        Map<String, String> result = new HashMap<String, String>();
267        result.put("batchId", batchId);
268        result.put("dropped", "true");
269        return buildFromMap(result);
270    }
271
272}