001/*
002 * (C) Copyright 2015-2018 Nuxeo (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 *     thibaud
018 */
019package org.nuxeo.diff.pictures;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.OutputStream;
024import java.io.Serializable;
025import java.util.HashMap;
026
027import javax.servlet.ServletException;
028import javax.servlet.http.HttpServlet;
029import javax.servlet.http.HttpServletRequest;
030import javax.servlet.http.HttpServletResponse;
031
032import org.apache.commons.lang3.StringUtils;
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.nuxeo.ecm.core.api.Blob;
036import org.nuxeo.ecm.core.api.CloseableCoreSession;
037import org.nuxeo.ecm.core.api.CoreInstance;
038import org.nuxeo.ecm.core.api.DocumentModel;
039import org.nuxeo.ecm.core.api.IdRef;
040import org.nuxeo.ecm.core.api.NuxeoException;
041import org.nuxeo.ecm.core.api.repository.RepositoryManager;
042import org.nuxeo.ecm.platform.commandline.executor.api.CommandNotAvailable;
043import org.nuxeo.ecm.platform.web.common.ServletHelper;
044import org.nuxeo.runtime.api.Framework;
045import org.nuxeo.runtime.transaction.TransactionHelper;
046
047/**
048 * /nuxeo/diffPictures?&repo=therepo&leftDocId=123456&rightDocId= 456789012
049 * &xpath=file:content&commandLine=thecommandline&fuzz=1000&highlightColor=Red &lowlightColor=White&altExtension=jpg
050 * <p>
051 * <ul>
052 * <li><code>leftDocId</code> and <code>rightDocId</code> are required</li>
053 * <li>All other parameters are optional (commandLine, xpath, fuzz, ...). Default values are defined in
054 * <code>DiffPictures</code></li>
055 * <li><code>altExtension</code> is special. If the pictures to compare are _not_ jpeg, png, or gif, _and_ if this
056 * parameter is set, then the result picture will be of this kind. Useful when comparing 2 psd or tif files for example,
057 * and the browser can't display them</li>
058 * </ul>
059 * <p>
060 * commandline, xpath, fuzz, highlightColor, lowlightColor and repo are optional
061 *
062 * @since 7.4
063 */
064public class DiffPicturesServlet extends HttpServlet {
065
066    private static final long serialVersionUID = 1L;
067
068    private static final Log log = LogFactory.getLog(DiffPicturesServlet.class);
069
070    protected static final int BUFFER_SIZE = 1024 * 512;
071
072    @Override
073    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
074
075        String leftDocId = req.getParameter("leftDocId");
076        String rightDocId = req.getParameter("rightDocId");
077
078        if (StringUtils.isBlank(leftDocId)) {
079            sendTextResponse(resp, "you must specify a left document as origin");
080            return;
081        }
082        if (StringUtils.isBlank(rightDocId)) {
083            sendTextResponse(resp, "you must specify 'right' used for comparison against the left document.");
084            return;
085        }
086
087        TransactionHelper.runInTransaction(() -> {
088
089            // WARNING: If you change the name of a parameter, also change it in nuxeo-diff-pictures.js
090            String repo = req.getParameter("repo");
091            String xpath = req.getParameter("xpath");
092            String commandLine = req.getParameter("commandLine");
093            String fuzz = req.getParameter("fuzz");
094            String highlightColor = req.getParameter("highlightColor");
095            String lowlightColor = req.getParameter("lowlightColor");
096            String altExtension = req.getParameter("altExtension");
097
098            if (StringUtils.isBlank(repo)) {
099                repo = Framework.getService(RepositoryManager.class).getDefaultRepository().getName();
100            }
101
102            // This try-with-resources does an implicit close() at the end
103            try (CloseableCoreSession coreSession = CoreInstance.openCoreSession(repo)) {
104
105                DocumentModel leftDoc = coreSession.getDocument(new IdRef(leftDocId));
106                DocumentModel rightDoc = coreSession.getDocument(new IdRef(rightDocId));
107
108                DiffPictures dp = new DiffPictures(leftDoc, rightDoc, xpath);
109
110                HashMap<String, Serializable> params = new HashMap<>();
111                if (StringUtils.isNotBlank(fuzz)) {
112                    params.put("fuzz", fuzz);
113                }
114                if (StringUtils.isNotBlank(highlightColor)) {
115                    params.put("highlightColor", highlightColor);
116                }
117                if (StringUtils.isNotBlank(lowlightColor)) {
118                    params.put("lowlightColor", lowlightColor);
119                }
120
121                if (StringUtils.isNotBlank(altExtension)) {
122                    // Using the leftDoc only
123                    Blob leftB;
124                    if (StringUtils.isBlank(xpath) || "null".equals(xpath)) {
125                        leftB = (Blob) leftDoc.getPropertyValue(DiffPictures.DEFAULT_XPATH);
126                    } else {
127                        leftB = (Blob) leftDoc.getPropertyValue(xpath);
128                    }
129                    String fileName = leftB.getFilename();
130                    int dotPos = fileName.lastIndexOf(".");
131                    String ext = fileName.substring(dotPos + 1);
132                    ext = ext.toLowerCase();
133                    switch (ext) {
134                    case "jpg":
135                    case "jpeg":
136                    case "png":
137                    case "gif":
138                        // No need to change anything
139                        break;
140
141                    default:
142                        if (altExtension.indexOf(".") != 0) {
143                            altExtension = "." + altExtension;
144                        }
145                        fileName = "comp-" + fileName + altExtension;
146                        params.put("targetFileName", fileName);
147                        break;
148
149                    }
150                }
151
152                Blob bResult;
153                try {
154                    bResult = dp.compare(commandLine, params);
155                } catch (CommandNotAvailable | IOException e) {
156                    log.error("Unable to compare the pictures", e);
157                    try {
158                        sendTextResponse(resp, "Unable to compare the pictures");
159                    } catch (IOException e1) {
160                        throw new NuxeoException(e1);
161                    }
162                    return;
163                }
164
165                resp.setHeader("Cache-Control", "no-cache");
166                resp.setHeader("Pragma", "no-cache");
167                try {
168                    sendBlobResult(req, resp, bResult);
169                } catch (IOException e) {
170                    log.error("Unable to handleCompareResult", e);
171                    try {
172                        sendTextResponse(resp, "Unable to return the result");
173                    } catch (IOException e1) {
174                        throw new NuxeoException(e1);
175                    }
176                    return;
177                }
178            }
179        });
180    }
181
182    protected void sendTextResponse(HttpServletResponse resp, String response) throws IOException {
183
184        resp.setContentType("text/plain");
185        resp.setContentLength(response.getBytes().length);
186        OutputStream out = resp.getOutputStream();
187        out.write(response.getBytes());
188        out.close();
189
190    }
191
192    protected void sendBlobResult(HttpServletRequest req, HttpServletResponse resp, Blob blob) throws IOException {
193
194        InputStream in = blob.getStream();
195        OutputStream out = resp.getOutputStream();
196        String fileName = blob.getFilename();
197
198        resp.setHeader("Content-Disposition", ServletHelper.getRFC2231ContentDisposition(req, fileName));
199        resp.setContentType(blob.getMimeType());
200        long fileSize = blob.getLength();
201        resp.setContentLength((int) fileSize);
202
203        byte[] buffer = new byte[BUFFER_SIZE];
204        int bytesRead;
205        while ((bytesRead = in.read(buffer)) != -1) {
206            out.write(buffer, 0, bytesRead);
207        }
208
209    }
210}