001/* 002 * (C) Copyright 2015 Nuxeo SA (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-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 * thibaud 016 */ 017package org.nuxeo.diff.pictures; 018 019import java.io.BufferedReader; 020import java.io.File; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.InputStreamReader; 024import java.io.Serializable; 025import java.util.Calendar; 026import java.util.HashMap; 027import java.util.Map; 028import java.util.Map.Entry; 029 030import org.apache.commons.lang.StringEscapeUtils; 031import org.apache.commons.lang.StringUtils; 032import org.nuxeo.ecm.core.api.Blob; 033import org.nuxeo.ecm.core.api.Blobs; 034import org.nuxeo.ecm.core.api.CloseableFile; 035import org.nuxeo.ecm.core.api.DocumentModel; 036import org.nuxeo.ecm.core.api.NuxeoException; 037import org.nuxeo.ecm.platform.commandline.executor.api.CmdParameters; 038import org.nuxeo.ecm.platform.commandline.executor.api.CommandLineExecutorService; 039import org.nuxeo.ecm.platform.commandline.executor.api.CommandNotAvailable; 040import org.nuxeo.ecm.platform.commandline.executor.api.ExecResult; 041import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper; 042import org.nuxeo.runtime.api.Framework; 043 044/** 045 * @since 7.4 046 */ 047public class DiffPictures { 048 049 public static final String DEFAULT_COMMAND = "diff-pictures-default"; 050 051 public static final String DEFAULT_XPATH = "file:content"; 052 053 public static final String DEFAULT_FUZZ = "0"; 054 055 public static final String DEFAULT_HIGHLIGHT_COLOR = "Red"; 056 057 public static final String DEFAULT_LOWLIGHT_COLOR = "None"; 058 059 protected static final String TEMP_DIR_PATH = System.getProperty("java.io.tmpdir"); 060 061 // See nuxeo-diff-pictures-template.html 062 protected static final String TMPL_PREFIX = "{{"; 063 064 protected static final String TMPL_SUFFIX = "}}"; 065 066 protected static final String TMPL_CONTEXT_PATH = buildTemplateKey("CONTEXT_PATH"); 067 068 protected static final String TMPL_ACTION = buildTemplateKey("ACTION"); 069 070 protected static final String TMPL_LEFT_DOC_ID = buildTemplateKey("LEFT_DOC_ID"); 071 072 protected static final String TMPL_LEFT_DOC_LABEL = buildTemplateKey("LEFT_DOC_LABEL"); 073 074 protected static final String TMPL_RIGHT_DOC_ID = buildTemplateKey("RIGHT_DOC_ID"); 075 076 protected static final String TMPL_RIGHT_DOC_LABEL = buildTemplateKey("RIGHT_DOC_LABEL"); 077 078 protected static final String TMPL_XPATH = buildTemplateKey("XPATH"); 079 080 protected static final String TMPL_TIME_STAMP = buildTemplateKey("TIME_STAMP"); 081 082 Blob b1; 083 084 Blob b2; 085 086 String leftDocId; 087 088 String rightDocId; 089 090 String commandLine; 091 092 Map<String, Serializable> clParameters; 093 094 protected static String buildTemplateKey(String inName) { 095 return TMPL_PREFIX + inName + TMPL_SUFFIX; 096 } 097 098 public DiffPictures(Blob inB1, Blob inB2) { 099 100 this(inB1, inB2, null, null); 101 102 } 103 104 public DiffPictures(DocumentModel inLeft, DocumentModel inRight) { 105 106 this(inLeft, inRight, null); 107 } 108 109 public DiffPictures(DocumentModel inLeft, DocumentModel inRight, String inXPath) { 110 111 Blob leftB, rightB; 112 113 if (StringUtils.isBlank(inXPath) || "null".equals(inXPath)) { 114 leftB = (Blob) inLeft.getPropertyValue(DEFAULT_XPATH); 115 rightB = (Blob) inRight.getPropertyValue(DEFAULT_XPATH); 116 } else { 117 leftB = (Blob) inLeft.getPropertyValue(inXPath); 118 rightB = (Blob) inRight.getPropertyValue(inXPath); 119 } 120 121 init(leftB, rightB, inLeft.getId(), inRight.getId()); 122 } 123 124 public DiffPictures(Blob inB1, Blob inB2, String inLeftDocId, String inRightDocId) { 125 init(inB1, inB2, inLeftDocId, inRightDocId); 126 127 } 128 129 private void init(Blob inB1, Blob inB2, String inLeftDocId, String inRightDocId) { 130 131 b1 = inB1; 132 b2 = inB2; 133 leftDocId = inLeftDocId; 134 rightDocId = inRightDocId; 135 } 136 137 public Blob compare(String inCommandLine, Map<String, Serializable> inParams) throws CommandNotAvailable, 138 IOException { 139 140 String finalName; 141 142 commandLine = StringUtils.isBlank(inCommandLine) ? DEFAULT_COMMAND : inCommandLine; 143 144 clParameters = inParams == null ? new HashMap<>() : inParams; 145 146 finalName = (String) clParameters.get("targetFileName"); 147 if (StringUtils.isBlank(finalName)) { 148 finalName = "comp-" + b1.getFilename(); 149 } 150 151 CloseableFile cf1 = null, cf2 = null; 152 String filePath1 = null, filePath2 = null; 153 CommandLineExecutorService cles = Framework.getService(CommandLineExecutorService.class); 154 CmdParameters params = cles.getDefaultCmdParameters(); 155 156 try { 157 cf1 = b1.getCloseableFile(); 158 filePath1 = cf1.getFile().getAbsolutePath(); 159 params.addNamedParameter("file1", filePath1); 160 161 cf2 = b2.getCloseableFile(); 162 filePath2 = cf2.getFile().getAbsolutePath(); 163 params.addNamedParameter("file2", filePath2); 164 165 checkDefaultParametersValues(); 166 for (Entry<String, Serializable> entry : clParameters.entrySet()) { 167 params.addNamedParameter(entry.getKey(), (String) entry.getValue()); 168 } 169 170 String destFilePath; 171 String destFileExtension; 172 173 int dotIndex = finalName.lastIndexOf('.'); 174 if (dotIndex < 0) { 175 destFileExtension = ".tmp"; 176 } else { 177 destFileExtension = finalName.substring(dotIndex); 178 } 179 180 Blob tempBlob = Blobs.createBlobWithExtension(destFileExtension); 181 destFilePath = tempBlob.getFile().getAbsolutePath(); 182 183 params.addNamedParameter("targetFilePath", destFilePath); 184 185 ExecResult execResult = cles.execCommand(commandLine, params); 186 187 // WARNING 188 // ImageMagick can return a non zero code with some of its commands, 189 // while the execution went totally OK, with no error. The problem is 190 // that the CommandLineExecutorService assumes a non-zero return code is 191 // an error => we must handle the thing by ourselves, basically just 192 // checking if we do have a comparison file created by ImageMagick 193 File tempDestFile = tempBlob.getFile(); 194 if (!tempDestFile.exists() || tempDestFile.length() < 1) { 195 throw new NuxeoException("Failed to execute the command <" + commandLine + ">. Final command [ " 196 + execResult.getCommandLine() + " ] returned with error " + execResult.getReturnCode(), 197 execResult.getError()); 198 } 199 200 tempBlob.setFilename(finalName); 201 202 return tempBlob; 203 204 } catch (IOException e) { 205 if (filePath1 == null) { 206 throw new IOException("Could not get a valid File from left blob.", e); 207 } 208 if (filePath2 == null) { 209 throw new IOException("Could not get a valid File from right blob.", e); 210 } 211 212 throw e; 213 214 } finally { 215 if (cf1 != null) { 216 cf1.close(); 217 } 218 if (cf2 != null) { 219 cf2.close(); 220 } 221 } 222 } 223 224 /* 225 * Adds the default values if a parameter is missing. This applies for all command lines (and some will be unused) 226 */ 227 protected void checkDefaultParametersValues() { 228 229 if (isDefaultValue((String) clParameters.get("fuzz"))) { 230 clParameters.put("fuzz", DEFAULT_FUZZ); 231 } 232 233 if (isDefaultValue((String) clParameters.get("highlightColor"))) { 234 clParameters.put("highlightColor", DEFAULT_HIGHLIGHT_COLOR); 235 } 236 237 if (isDefaultValue((String) clParameters.get("lowlightColor"))) { 238 clParameters.put("lowlightColor", DEFAULT_LOWLIGHT_COLOR); 239 } 240 241 } 242 243 protected boolean isDefaultValue(String inValue) { 244 return StringUtils.isBlank(inValue) || inValue.toLowerCase().equals("default"); 245 } 246 247 public static String buildDiffHtml(DocumentModel leftDoc, DocumentModel rightDoc, String xpath) throws IOException { 248 String html = ""; 249 InputStream in = null; 250 try { 251 in = DiffPictures.class.getResourceAsStream("/files/nuxeo-diff-pictures-template.html"); 252 BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 253 String line; 254 while ((line = reader.readLine()) != null) { 255 html += line + "\n"; 256 } 257 258 } finally { 259 if (in != null) { 260 in.close(); 261 } 262 } 263 264 String leftDocId = leftDoc.getId(); 265 String rightDocId = rightDoc.getId(); 266 String leftLabel = leftDoc.getTitle(); 267 String rightLabel = rightDoc.getTitle(); 268 if (leftLabel.equals(rightLabel)) { 269 if (leftDoc.isVersion()) { 270 leftLabel = "Version " + leftDoc.getVersionLabel(); 271 } 272 if (rightDoc.isVersion()) { 273 rightLabel = "Version " + rightDoc.getVersionLabel(); 274 } 275 } 276 277 html = html.replace(TMPL_CONTEXT_PATH, VirtualHostHelper.getContextPathProperty()); 278 html = html.replace(TMPL_ACTION, "diff"); 279 html = html.replace(TMPL_LEFT_DOC_ID, 280 StringEscapeUtils.escapeJavaScript(StringEscapeUtils.escapeHtml(leftDocId))); 281 html = html.replace(TMPL_LEFT_DOC_LABEL, 282 StringEscapeUtils.escapeJavaScript(StringEscapeUtils.escapeHtml(leftLabel))); 283 html = html.replace(TMPL_RIGHT_DOC_ID, 284 StringEscapeUtils.escapeJavaScript(StringEscapeUtils.escapeHtml(rightDocId))); 285 html = html.replace(TMPL_RIGHT_DOC_LABEL, 286 StringEscapeUtils.escapeJavaScript(StringEscapeUtils.escapeHtml(rightLabel))); 287 if (StringUtils.isBlank(xpath) || xpath.toLowerCase().equals("default")) { 288 xpath = DEFAULT_XPATH; 289 } 290 html = html.replace(TMPL_XPATH, StringEscapeUtils.escapeJavaScript(StringEscapeUtils.escapeHtml(xpath))); 291 // dc:modified can be null... When running the Unit Tests for example 292 String lastModification; 293 Calendar cal = (Calendar) rightDoc.getPropertyValue("dc:modified"); 294 if (cal == null) { 295 lastModification = "" + Calendar.getInstance().getTimeInMillis(); 296 } else { 297 lastModification = "" + cal.getTimeInMillis(); 298 } 299 html = html.replace(TMPL_TIME_STAMP, lastModification); 300 301 return html; 302 } 303 304}