001/* 002 * Copyright 2017 Anyware Services 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 */ 016package org.ametys.plugins.extraction.edition; 017 018import java.io.File; 019import java.io.IOException; 020import java.io.OutputStream; 021import java.nio.file.Files; 022import java.nio.file.Paths; 023import java.nio.file.StandardCopyOption; 024import java.util.LinkedHashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.Properties; 028import java.util.regex.Matcher; 029import java.util.regex.Pattern; 030 031import javax.xml.transform.OutputKeys; 032import javax.xml.transform.TransformerConfigurationException; 033import javax.xml.transform.TransformerFactory; 034import javax.xml.transform.TransformerFactoryConfigurationError; 035import javax.xml.transform.sax.SAXTransformerFactory; 036import javax.xml.transform.sax.TransformerHandler; 037import javax.xml.transform.stream.StreamResult; 038 039import org.apache.avalon.framework.component.Component; 040import org.apache.avalon.framework.logger.AbstractLogEnabled; 041import org.apache.avalon.framework.service.ServiceException; 042import org.apache.avalon.framework.service.ServiceManager; 043import org.apache.avalon.framework.service.Serviceable; 044import org.apache.cocoon.xml.AttributesImpl; 045import org.apache.cocoon.xml.XMLUtils; 046import org.apache.commons.lang.StringUtils; 047import org.apache.excalibur.source.Source; 048import org.apache.excalibur.source.SourceResolver; 049import org.apache.excalibur.source.impl.FileSource; 050import org.apache.xml.serializer.OutputPropertiesFactory; 051import org.xml.sax.SAXException; 052 053import org.ametys.plugins.extraction.ExtractionConstants; 054 055/** 056 * Helper that manages the button that saves extraction's modifications 057 */ 058public class SaveExtractionHelper extends AbstractLogEnabled implements Component, Serviceable 059{ 060 /** The Avalon role name */ 061 public static final String ROLE = SaveExtractionHelper.class.getName(); 062 063 private static final String EXTRACT_EXTRA_DATA_REGEX = "\\(([-_a-zA-Z]+)\\)"; 064 065 private SourceResolver _sourceResolver; 066 067 public void service(ServiceManager serviceManager) throws ServiceException 068 { 069 _sourceResolver = (SourceResolver) serviceManager.lookup(SourceResolver.ROLE); 070 } 071 072 /** 073 * Saves modifications on extraction. Creates the definition file if it doesn't exist 074 * @param definitionFileName The extraction definition file name 075 * @param extraction A <code>Map</code> containing definition informations 076 * @return <code>true</code> if extraction saving succeed, <code>false</code> otherwise 077 * @throws Exception if an error occurs 078 */ 079 public boolean saveExtraction(String definitionFileName, Map<String, Object> extraction) throws Exception 080 { 081 boolean errorOccurred = false; 082 083 String backupFilePath = ExtractionConstants.DEFINITIONS_DIR + definitionFileName + ".tmp"; 084 Source backupSrc = _sourceResolver.resolveURI(backupFilePath); 085 File backupFile = ((FileSource) backupSrc).getFile(); 086 087 String definitionFilePath = ExtractionConstants.DEFINITIONS_DIR + definitionFileName; 088 Source definitionSrc = _sourceResolver.resolveURI(definitionFilePath); 089 File definitionFile = ((FileSource) definitionSrc).getFile(); 090 091 if (!definitionFile.exists()) 092 { 093 throw new IllegalArgumentException("The file " + definitionFilePath + " does not exist."); 094 } 095 096 // Create a backup file 097 try 098 { 099 Files.copy(definitionFile.toPath(), backupFile.toPath()); 100 } 101 catch (IOException e) 102 { 103 if (getLogger().isErrorEnabled()) 104 { 105 getLogger().error("Error when creating backup '" + definitionFileName + "' file", e); 106 } 107 } 108 109 try (OutputStream os = Files.newOutputStream(Paths.get(definitionFile.getAbsolutePath()))) 110 { 111 // Create a transformer for saving sax into a file 112 TransformerHandler handler = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler(); 113 114 StreamResult result = new StreamResult(os); 115 handler.setResult(result); 116 117 // create the format of result 118 Properties format = new Properties(); 119 format.put(OutputKeys.METHOD, "xml"); 120 format.put(OutputKeys.INDENT, "yes"); 121 format.put(OutputKeys.ENCODING, "UTF-8"); 122 format.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "4"); 123 handler.getTransformer().setOutputProperties(format); 124 125 // sax the config 126 try 127 { 128 _saxExtraction(extraction, handler); 129 } 130 catch (Exception e) 131 { 132 if (getLogger().isErrorEnabled()) 133 { 134 getLogger().error("Error when saxing the extraction defintion file '" + definitionFileName + "'", e); 135 } 136 errorOccurred = true; 137 } 138 } 139 catch (IOException | TransformerConfigurationException | TransformerFactoryConfigurationError e) 140 { 141 if (getLogger().isErrorEnabled()) 142 { 143 getLogger().error("Error when trying to modify the extraction definition file '" + definitionFileName + "'", e); 144 } 145 } 146 147 try 148 { 149 // Restore the file if an error previously occured and delete backup 150 if (errorOccurred) 151 { 152 Files.copy(backupFile.toPath(), definitionFile.toPath(), StandardCopyOption.REPLACE_EXISTING); 153 } 154 Files.deleteIfExists(backupFile.toPath()); 155 } 156 catch (IOException e) 157 { 158 if (getLogger().isErrorEnabled()) 159 { 160 getLogger().error("Error when restoring backup '" + definitionFileName + "' file", e); 161 } 162 } 163 164 return !errorOccurred; 165 } 166 167 @SuppressWarnings("unchecked") 168 private void _saxExtraction(Map<String, Object> extraction, TransformerHandler handler) throws SAXException 169 { 170 handler.startDocument(); 171 172 // Set extraction's name attribute 173 Map<String, Object> extractionData = (Map<String, Object>) extraction.get("data"); 174 AttributesImpl attributes = new AttributesImpl(); 175 if (null != extractionData && !extractionData.isEmpty()) 176 { 177 attributes.addCDATAAttribute("name", (String) extractionData.get("name")); 178 } 179 XMLUtils.startElement(handler, "extraction", attributes); 180 181 List<Map<String, Object>> children = (List<Map<String, Object>>) extraction.get("children"); 182 if (null != children) 183 { 184 for (Map<String, Object> child : children) 185 { 186 String tag = (String) child.get("tag"); 187 switch (tag) 188 { 189 case ExtractionConstants.CLAUSES_VARIABLES_TAG: 190 _saxClausesVariables(child, handler); 191 break; 192 case ExtractionConstants.OPTIONAL_COLUMNS_TAG: 193 _saxOptionalColumns(child, handler); 194 break; 195 case ExtractionConstants.QUERY_COMPONENT_TAG: 196 case ExtractionConstants.THESAURUS_COMPONENT_TAG: 197 case ExtractionConstants.COUNT_COMPONENT_TAG: 198 case ExtractionConstants.MAPPING_QUERY_COMPONENT_TAG: 199 _saxExtractionComponent(child, handler); 200 break; 201 default: 202 break; 203 } 204 } 205 } 206 207 XMLUtils.endElement(handler, "extraction"); 208 handler.endDocument(); 209 } 210 211 @SuppressWarnings("unchecked") 212 private void _saxClausesVariables(Map<String, Object> child, TransformerHandler handler) throws SAXException 213 { 214 Map<String, Object> data = (Map<String, Object>) child.get("data"); 215 216 List<Map<String, Object>> variables = (List<Map<String, Object>>) data.get("variables"); 217 if (!variables.isEmpty()) 218 { 219 XMLUtils.startElement(handler, ExtractionConstants.CLAUSES_VARIABLES_TAG); 220 for (Map<String, Object> variable : variables) 221 { 222 AttributesImpl attributes = new AttributesImpl(); 223 attributes.addCDATAAttribute("name", (String) variable.get("name")); 224 attributes.addCDATAAttribute("contentType", (String) variable.get("contentType")); 225 XMLUtils.createElement(handler, "variable", attributes); 226 } 227 XMLUtils.endElement(handler, ExtractionConstants.CLAUSES_VARIABLES_TAG); 228 } 229 } 230 231 @SuppressWarnings("unchecked") 232 private void _saxOptionalColumns(Map<String, Object> child, TransformerHandler handler) throws SAXException 233 { 234 Map<String, Object> data = (Map<String, Object>) child.get("data"); 235 236 List<String> names = (List<String>) data.get("names"); 237 if (null != names && !names.isEmpty()) 238 { 239 XMLUtils.startElement(handler, ExtractionConstants.OPTIONAL_COLUMNS_TAG); 240 for (String name : names) 241 { 242 XMLUtils.startElement(handler, "name"); 243 XMLUtils.data(handler, name.trim()); 244 XMLUtils.endElement(handler, "name"); 245 } 246 XMLUtils.endElement(handler, ExtractionConstants.OPTIONAL_COLUMNS_TAG); 247 } 248 } 249 250 @SuppressWarnings("unchecked") 251 private void _saxExtractionComponent(Map<String, Object> component, TransformerHandler handler) throws SAXException 252 { 253 Map<String, Object> data = (Map<String, Object>) component.get("data"); 254 255 String tag = (String) component.get("tag"); 256 AttributesImpl attributes = _getComponentAttibutes(data); 257 XMLUtils.startElement(handler, tag, attributes); 258 259 // sax component's elements 260 _saxExtractionComponentClauses(data, handler); 261 _saxExtractionComponentGroupingFields(data, handler); 262 _saxExtractionComponentColumns(data, handler); 263 _saxExtractionComponentSorts(data, handler); 264 265 // process children 266 if (component.get("children") != null) 267 { 268 List<Map<String, Object>> children = (List<Map<String, Object>>) component.get("children"); 269 for (Map<String, Object> child : children) 270 { 271 _saxExtractionComponent(child, handler); 272 } 273 } 274 275 XMLUtils.endElement(handler, tag); 276 } 277 278 @SuppressWarnings("unchecked") 279 private AttributesImpl _getComponentAttibutes(Map<String, Object> data) 280 { 281 AttributesImpl attributes = new AttributesImpl(); 282 283 Object componentTagName = data.get("componentTagName"); 284 if (null != componentTagName && !StringUtils.isEmpty((String) componentTagName)) 285 { 286 attributes.addCDATAAttribute("tagName", (String) componentTagName); 287 } 288 289 List<String> contentTypes = (List<String>) data.get("contentTypes"); 290 if (null != contentTypes && !contentTypes.isEmpty()) 291 { 292 attributes.addCDATAAttribute("contentTypes", String.join(ExtractionConstants.STRING_COLLECTIONS_INPUT_DELEMITER, contentTypes)); 293 } 294 295 Object queryReferenceId = data.get("queryReferenceId"); 296 if (null != queryReferenceId && !StringUtils.isEmpty((String) queryReferenceId)) 297 { 298 attributes.addCDATAAttribute("ref", (String) queryReferenceId); 299 } 300 301 Object thesaurusName = data.get("thesaurusName"); 302 if (null != thesaurusName && !StringUtils.isEmpty((String) thesaurusName)) 303 { 304 attributes.addCDATAAttribute("thesaurusName", (String) thesaurusName); 305 } 306 307 Object microThesaurusName = data.get("microThesaurusName"); 308 if (null != microThesaurusName && !StringUtils.isEmpty((String) microThesaurusName)) 309 { 310 attributes.addCDATAAttribute("microThesaurusName", (String) microThesaurusName); 311 } 312 313 Object maxLevel = data.get("maxLevel"); 314 if (null != maxLevel && !StringUtils.isEmpty(String.valueOf(maxLevel))) 315 { 316 attributes.addCDATAAttribute("max-level", String.valueOf(maxLevel)); 317 } 318 319 return attributes; 320 } 321 322 @SuppressWarnings("unchecked") 323 private void _saxExtractionComponentClauses(Map<String, Object> data, TransformerHandler handler) throws SAXException 324 { 325 List<String> clauses = (List<String>) data.get("clauses"); 326 if (null != clauses && !clauses.isEmpty()) 327 { 328 XMLUtils.startElement(handler, "clauses"); 329 for (String clause : clauses) 330 { 331 XMLUtils.startElement(handler, "clause"); 332 XMLUtils.data(handler, clause); 333 XMLUtils.endElement(handler, "clause"); 334 } 335 XMLUtils.endElement(handler, "clauses"); 336 } 337 } 338 339 private void _saxExtractionComponentGroupingFields(Map<String, Object> data, TransformerHandler handler) throws SAXException 340 { 341 Object groupingFields = data.get("groupingFields"); 342 if (null == groupingFields || StringUtils.isEmpty((String) groupingFields)) 343 { 344 return; 345 } 346 XMLUtils.startElement(handler, "grouping-fields"); 347 XMLUtils.data(handler, (String) groupingFields); 348 XMLUtils.endElement(handler, "grouping-fields"); 349 } 350 351 private void _saxExtractionComponentColumns(Map<String, Object> data, TransformerHandler handler) throws SAXException 352 { 353 Object columnsObj = data.get("columns"); 354 if (null == columnsObj || StringUtils.isEmpty((String) columnsObj)) 355 { 356 return; 357 } 358 359 Map<String, String> columns = _splitDataAndExtradataFromString((String) columnsObj); 360 361 if (!columns.isEmpty()) 362 { 363 AttributesImpl columnsAttributes = new AttributesImpl(); 364 Object overrideColumns = data.get("overrideColumns"); 365 if (null != overrideColumns && (Boolean) overrideColumns) 366 { 367 columnsAttributes.addCDATAAttribute("override", "true"); 368 } 369 XMLUtils.startElement(handler, "columns", columnsAttributes); 370 371 for (Map.Entry<String, String> column : columns.entrySet()) 372 { 373 AttributesImpl columnAttributes = new AttributesImpl(); 374 if (column.getValue() != null) 375 { 376 columnAttributes.addCDATAAttribute("optional", column.getValue()); 377 } 378 XMLUtils.startElement(handler, "column", columnAttributes); 379 XMLUtils.data(handler, column.getKey()); 380 XMLUtils.endElement(handler, "column"); 381 } 382 383 XMLUtils.endElement(handler, "columns"); 384 } 385 } 386 387 private void _saxExtractionComponentSorts(Map<String, Object> data, TransformerHandler handler) throws SAXException 388 { 389 Object sortsObj = data.get("sorts"); 390 if (null == sortsObj || StringUtils.isEmpty((String) sortsObj)) 391 { 392 return; 393 } 394 395 Map<String, String> sorts = _splitDataAndExtradataFromString((String) sortsObj); 396 397 if (!sorts.isEmpty()) 398 { 399 AttributesImpl sortsAttributes = new AttributesImpl(); 400 Object overrideSorts = data.get("overrideSorts"); 401 if (null != overrideSorts && (Boolean) overrideSorts) 402 { 403 sortsAttributes.addCDATAAttribute("override", "true"); 404 } 405 XMLUtils.startElement(handler, "sorts", sortsAttributes); 406 407 for (Map.Entry<String, String> sort : sorts.entrySet()) 408 { 409 AttributesImpl sortAttributes = new AttributesImpl(); 410 if (sort.getValue() != null) 411 { 412 sortAttributes.addCDATAAttribute("order", sort.getValue()); 413 } 414 XMLUtils.startElement(handler, "sort", sortAttributes); 415 XMLUtils.data(handler, sort.getKey()); 416 XMLUtils.endElement(handler, "sort"); 417 } 418 419 XMLUtils.endElement(handler, "sorts"); 420 } 421 } 422 423 private Map<String, String> _splitDataAndExtradataFromString(String str) 424 { 425 Map<String, String> result = new LinkedHashMap<>(); 426 427 for (String data : str.split(ExtractionConstants.STRING_COLLECTIONS_INPUT_DELEMITER)) 428 { 429 Pattern pattern = Pattern.compile(EXTRACT_EXTRA_DATA_REGEX); 430 Matcher matcher = pattern.matcher(data); 431 432 String extra = null; 433 if (matcher.find()) 434 { 435 extra = matcher.group(1); 436 } 437 438 String finalData = data; 439 if (null != extra) 440 { 441 extra = extra.trim(); 442 int indexOfExtra = data.indexOf("("); 443 finalData = data.substring(0, indexOfExtra); 444 } 445 446 result.put(finalData.trim(), extra); 447 } 448 449 return result; 450 } 451 452}