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