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