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.Path; 023import java.nio.file.Paths; 024import java.nio.file.StandardCopyOption; 025import java.util.Collections; 026import java.util.List; 027import java.util.Map; 028import java.util.Optional; 029import java.util.Properties; 030import java.util.function.BiConsumer; 031import java.util.stream.Collectors; 032import java.util.stream.IntStream; 033import java.util.stream.Stream; 034 035import javax.xml.parsers.DocumentBuilder; 036import javax.xml.parsers.DocumentBuilderFactory; 037import javax.xml.transform.OutputKeys; 038import javax.xml.transform.Transformer; 039import javax.xml.transform.TransformerFactory; 040import javax.xml.transform.dom.DOMSource; 041import javax.xml.transform.sax.SAXTransformerFactory; 042import javax.xml.transform.sax.TransformerHandler; 043import javax.xml.transform.stream.StreamResult; 044 045import org.apache.avalon.framework.service.ServiceException; 046import org.apache.avalon.framework.service.ServiceManager; 047import org.apache.cocoon.xml.AttributesImpl; 048import org.apache.cocoon.xml.XMLUtils; 049import org.apache.commons.lang3.StringUtils; 050import org.apache.excalibur.source.Source; 051import org.apache.excalibur.source.SourceResolver; 052import org.apache.excalibur.source.impl.FileSource; 053import org.apache.xml.serializer.OutputPropertiesFactory; 054import org.w3c.dom.Document; 055import org.w3c.dom.Element; 056import org.w3c.dom.NodeList; 057 058import org.ametys.cms.repository.ContentDAO; 059import org.ametys.cms.workflow.ContentWorkflowHelper; 060import org.ametys.core.ui.Callable; 061import org.ametys.core.ui.StaticClientSideElement; 062import org.ametys.core.user.UserIdentity; 063import org.ametys.core.util.I18nUtils; 064import org.ametys.plugins.core.user.UserHelper; 065import org.ametys.plugins.extraction.ExtractionConstants; 066import org.ametys.plugins.extraction.execution.Extraction.ExtractionProfile; 067import org.ametys.plugins.extraction.execution.Extraction.Visibility; 068import org.ametys.runtime.i18n.I18nizableText; 069 070/** 071 * This client site element manages a button to create an extraction definition file 072 */ 073public class EditExtractionClientSideElement extends StaticClientSideElement 074{ 075 /** The Avalon role name */ 076 public static final String ROLE = EditExtractionClientSideElement.class.getName(); 077 078 private SourceResolver _sourceResolver; 079 private ContentWorkflowHelper _contentWorkflowHelper; 080 private I18nUtils _i18nUtils; 081 private ContentDAO _contentDAO; 082 private UserHelper _userHelper; 083 084 @Override 085 public void service(ServiceManager serviceManager) throws ServiceException 086 { 087 super.service(serviceManager); 088 _sourceResolver = (SourceResolver) serviceManager.lookup(SourceResolver.ROLE); 089 _contentWorkflowHelper = (ContentWorkflowHelper) serviceManager.lookup(ContentWorkflowHelper.ROLE); 090 _i18nUtils = (I18nUtils) serviceManager.lookup(I18nUtils.ROLE); 091 _contentDAO = (ContentDAO) serviceManager.lookup(ContentDAO.ROLE); 092 _userHelper = (UserHelper) serviceManager.lookup(UserHelper.ROLE); 093 } 094 095 /** 096 * Creates an extraction definition file. 097 * @param relativeDefinitionFilePath The path of the extraction definition file to create. This path has to be relative to the base definition directory. 098 * @param language the language used to create the description 099 * @return Map containing success boolean and the created extraction informations, or error codes if one occurs 100 * @throws Exception if an error occurs 101 */ 102 @Callable (right = ExtractionConstants.MODIFY_EXTRACTION_RIGHT_ID) 103 public Map<String, Object> createExtraction(String relativeDefinitionFilePath, String language) throws Exception 104 { 105 // Create extraction definitions directory 106 Source definitionsSrc = _sourceResolver.resolveURI(ExtractionConstants.DEFINITIONS_DIR); 107 File definitionsDir = ((FileSource) definitionsSrc).getFile(); 108 definitionsDir.mkdirs(); 109 110 String absoluteDefinitionFilePath = ExtractionConstants.DEFINITIONS_DIR + relativeDefinitionFilePath; 111 Source definitionSrc = _sourceResolver.resolveURI(absoluteDefinitionFilePath); 112 File definitionFile = ((FileSource) definitionSrc).getFile(); 113 114 if (definitionFile.exists()) 115 { 116 getLogger().error("A definition file already exists at path '{}'", relativeDefinitionFilePath); 117 return Map.of( 118 "success", false, 119 "error", "already-exists"); 120 } 121 122 try (OutputStream os = Files.newOutputStream(Paths.get(definitionFile.getAbsolutePath()))) 123 { 124 // Create the description content 125 String extractionName = _getExtractionNameFromFileName(definitionFile.getName()); 126 I18nizableText descriptionTitle = new I18nizableText(ExtractionConstants.PLUGIN_NAME, ExtractionConstants.DESCRIPTION_DEFAULT_TITLE_KEY, Collections.singletonList(extractionName)); 127 128 Map<String, Object> contentInfos = _contentWorkflowHelper.createContent( 129 ExtractionConstants.DESCRIPTION_CONTENT_WORKFLOW_NAME, 130 ExtractionConstants.DESCRIPTION_CONTENT_INITIAL_ACTION_ID, 131 extractionName, 132 _i18nUtils.translate(descriptionTitle, language), 133 new String[] {ExtractionConstants.DESCRIPTION_CONTENT_TYPE_ID}, 134 new String[0], 135 language 136 ); 137 String descriptionId = (String) contentInfos.get("contentId"); 138 139 // Create a transformer for saving sax into a file 140 TransformerHandler handler = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler(); 141 142 StreamResult result = new StreamResult(os); 143 handler.setResult(result); 144 145 // create the format of result 146 Properties format = new Properties(); 147 format.put(OutputKeys.METHOD, "xml"); 148 format.put(OutputKeys.INDENT, "yes"); 149 format.put(OutputKeys.ENCODING, "UTF-8"); 150 format.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "4"); 151 handler.getTransformer().setOutputProperties(format); 152 handler.startDocument(); 153 154 // sax skeleton 155 XMLUtils.startElement(handler, ExtractionConstants.EXTRACTION_TAG); 156 157 // Generate SAX events for the extraction's description 158 AttributesImpl attributes = new AttributesImpl(); 159 attributes.addCDATAAttribute(ExtractionConstants.DESCRIPTION_IDENTIFIER_ATTRIBUTE_NAME, descriptionId); 160 XMLUtils.createElement(handler, ExtractionConstants.DESCRIPTION_TAG, attributes); 161 162 // Generate SAX events for the extraction's visibility 163 String visibilityAsString = Visibility.PRIVATE.toString(); 164 XMLUtils.createElement(handler, ExtractionConstants.VISIBILITY_TAG, visibilityAsString); 165 166 // Generate SAX events for the extraction's author 167 UserIdentity author = _currentUserProvider.getUser(); 168 _userHelper.saxUserIdentity(author, handler, ExtractionConstants.AUTHOR_TAG); 169 170 XMLUtils.endElement(handler, ExtractionConstants.EXTRACTION_TAG); 171 172 handler.endDocument(); 173 174 return Map.of( 175 "success", true, 176 "path", relativeDefinitionFilePath, 177 "name", definitionFile.getName(), 178 "descriptionId", descriptionId, 179 "visibility", visibilityAsString, 180 "author", _userHelper.user2json(author), 181 "canRead", true, 182 "canWrite", true); 183 } 184 catch (Exception e) 185 { 186 getLogger().error("Error when trying to create the extraction definition file '{}'", relativeDefinitionFilePath, e); 187 return Map.of( 188 "success", false, 189 "error", "other-error"); 190 } 191 } 192 193 private String _getExtractionNameFromFileName(String fileName) 194 { 195 return fileName.contains(".") ? fileName.substring(0, fileName.lastIndexOf(".")) : fileName; 196 } 197 198 /** 199 * Adds a description to an extraction. 200 * @param definitionFileName The extraction definition file name 201 * @param descriptionId the identifier of the description 202 * @return Map containing success boolean and error codes if one occurs 203 * @throws Exception if an error occurs 204 */ 205 @Callable (right = ExtractionConstants.MODIFY_EXTRACTION_RIGHT_ID) 206 public Map<String, Object> addDescription(String definitionFileName, String descriptionId) throws Exception 207 { 208 return _modifyDefinitionFile(definitionFileName, descriptionId, this::_insertDescriptionInDocument); 209 } 210 211 private void _insertDescriptionInDocument(Document document, String descriptionId) 212 { 213 Element extractionRoot = document.getDocumentElement(); 214 215 // Delete the current description node if exists 216 _getElements(document.getDocumentElement(), ExtractionConstants.DESCRIPTION_TAG) 217 .forEach(extractionRoot::removeChild); 218 219 // Insert the description in the extraction root node 220 Element description = document.createElement(ExtractionConstants.DESCRIPTION_TAG); 221 description.setAttribute(ExtractionConstants.DESCRIPTION_IDENTIFIER_ATTRIBUTE_NAME, descriptionId); 222 extractionRoot.insertBefore(description, extractionRoot.getFirstChild()); 223 } 224 225 /** 226 * Changes the visibility of an extraction 227 * @param definitionFileName The extraction definition file name 228 * @param visibilityStr The new visibility 229 * @return Map containing success boolean and error codes if one occurs 230 * @throws Exception if an error occurs 231 */ 232 @Callable (right = ExtractionConstants.MODIFY_EXTRACTION_RIGHT_ID) 233 public Map<String, Object> changeVisibility(String definitionFileName, String visibilityStr) throws Exception 234 { 235 return _modifyDefinitionFile(definitionFileName, visibilityStr, this::_changeVisibilityInDocument); 236 } 237 238 private void _changeVisibilityInDocument(Document document, String visibilityStr) 239 { 240 Element extractionRoot = document.getDocumentElement(); 241 242 // Delete the current visibility nodes if exist 243 _getElements(document.getDocumentElement(), ExtractionConstants.VISIBILITY_TAG) 244 .forEach(extractionRoot::removeChild); 245 246 // Insert the visibility in the extraction root node 247 Element visibility = document.createElement(ExtractionConstants.VISIBILITY_TAG); 248 visibility.setTextContent(visibilityStr); 249 extractionRoot.insertBefore(visibility, extractionRoot.getFirstChild()); 250 } 251 252 /** 253 * Assign rights to the given users on the given extraction 254 * @param definitionFileName The extraction definition file name 255 * @param profileId The profile id 256 * @param users The users to grant 257 * @return A result map 258 * @throws Exception if an error occurs 259 */ 260 @Callable 261 public Map<String, Object> addGrantedUsers(String definitionFileName, String profileId, List<Map<String, String>> users) throws Exception 262 { 263 ExtractionProfile profile = ExtractionProfile.valueOf(profileId.toUpperCase()); 264 Map<String, Object> arguments = Map.of( 265 "profile", profile, 266 "users", users); 267 return _modifyDefinitionFile(definitionFileName, arguments, this::_addGrantedUsers); 268 } 269 270 @SuppressWarnings("unchecked") 271 private void _addGrantedUsers(Document document, Map<String, Object> arguments) 272 { 273 ExtractionProfile profile = (ExtractionProfile) arguments.get("profile"); 274 String rightAccessElementTagName = ExtractionProfile.READ_ACCESS.equals(profile) ? ExtractionConstants.READ_ACCESS_TAG : ExtractionConstants.WRITE_ACCESS_TAG; 275 276 Element extractionRoot = document.getDocumentElement(); 277 Element rightAccessElement = _getOrCreateFirstElement(document, extractionRoot, rightAccessElementTagName); 278 Element usersElement = _getOrCreateFirstElement(document, rightAccessElement, ExtractionConstants.USERS_TAG); 279 280 List<Map<String, String>> users = (List<Map<String, String>>) arguments.get("users"); 281 for (Map<String, String> user : users) 282 { 283 Element userElement = document.createElement(ExtractionConstants.USER_TAG); 284 userElement.setAttribute("login", user.get("login")); 285 userElement.setAttribute("population", user.get("populationId")); 286 usersElement.appendChild(userElement); 287 } 288 } 289 290 /** 291 * Assign rights to the given groups on the given extraction 292 * @param definitionFileName The extraction definition file name 293 * @param profileId The profile id 294 * @param groups The groups to grant 295 * @return A result map 296 * @throws Exception if an error occurs 297 */ 298 @Callable 299 public Map<String, Object> addGrantedGroups(String definitionFileName, String profileId, List<Map<String, String>> groups) throws Exception 300 { 301 ExtractionProfile profile = ExtractionProfile.valueOf(profileId.toUpperCase()); 302 Map<String, Object> arguments = Map.of( 303 "profile", profile, 304 "groups", groups); 305 return _modifyDefinitionFile(definitionFileName, arguments, this::_addGrantedGroups); 306 } 307 308 @SuppressWarnings("unchecked") 309 private void _addGrantedGroups(Document document, Map<String, Object> arguments) 310 { 311 ExtractionProfile profile = (ExtractionProfile) arguments.get("profile"); 312 String rightAccessElementTagName = ExtractionProfile.READ_ACCESS.equals(profile) ? ExtractionConstants.READ_ACCESS_TAG : ExtractionConstants.WRITE_ACCESS_TAG; 313 314 Element extractionRoot = document.getDocumentElement(); 315 Element rightAccessElement = _getOrCreateFirstElement(document, extractionRoot, rightAccessElementTagName); 316 Element groupsElement = _getOrCreateFirstElement(document, rightAccessElement, ExtractionConstants.GROUPS_TAG); 317 318 List<Map<String, String>> groups = (List<Map<String, String>>) arguments.get("groups"); 319 for (Map<String, String> group : groups) 320 { 321 Element groupElement = document.createElement(ExtractionConstants.GROUP_TAG); 322 groupElement.setAttribute("id", group.get("id")); 323 groupElement.setAttribute("groupDirectory", group.get("groupDirectory")); 324 groupsElement.appendChild(groupElement); 325 } 326 } 327 328 /** 329 * Remove rights to the given users on the given query 330 * @param definitionFileName The extraction definition file name 331 * @param profileId The profile id 332 * @param users The users to remove 333 * @param groups The groups to remove 334 * @return A result map 335 * @throws Exception if an error occurs 336 */ 337 @Callable 338 public Map<String, Object> removeAssignment(String definitionFileName, String profileId, List<Map<String, String>> users, List<Map<String, String>> groups) throws Exception 339 { 340 ExtractionProfile profile = ExtractionProfile.valueOf(profileId.toUpperCase()); 341 Map<String, Object> arguments = Map.of( 342 "profile", profile, 343 "users", users, 344 "groups", groups); 345 return _modifyDefinitionFile(definitionFileName, arguments, this::_removeAssignement); 346 } 347 348 @SuppressWarnings("unchecked") 349 private void _removeAssignement(Document document, Map<String, Object> arguments) 350 { 351 ExtractionProfile profile = (ExtractionProfile) arguments.get("profile"); 352 String rightAccessElementTagName = ExtractionProfile.READ_ACCESS.equals(profile) ? ExtractionConstants.READ_ACCESS_TAG : ExtractionConstants.WRITE_ACCESS_TAG; 353 354 Element extractionRoot = document.getDocumentElement(); 355 Optional<Element> optRightAccessElement = _getFirstElement(extractionRoot, rightAccessElementTagName); 356 357 if (optRightAccessElement.isPresent()) 358 { 359 Element rightAccessElement = optRightAccessElement.get(); 360 361 // Users 362 List<Map<String, String>> users = (List<Map<String, String>>) arguments.get("users"); 363 Optional<Element> usersElement = _getFirstElement(rightAccessElement, ExtractionConstants.USERS_TAG); 364 if (usersElement.isPresent()) 365 { 366 _getElements(document.getDocumentElement(), ExtractionConstants.USER_TAG) 367 .filter(element -> _containsUser(users, element)) 368 .forEach(extractionRoot::removeChild); 369 } 370 371 // Groups 372 List<Map<String, String>> groups = (List<Map<String, String>>) arguments.get("groups"); 373 Optional<Element> groupsElement = _getFirstElement(rightAccessElement, ExtractionConstants.GROUPS_TAG); 374 if (groupsElement.isPresent()) 375 { 376 _getElements(document.getDocumentElement(), ExtractionConstants.GROUP_TAG) 377 .filter(element -> _containsGroup(groups, element)) 378 .forEach(extractionRoot::removeChild); 379 } 380 } 381 } 382 383 private boolean _containsUser(List<Map<String, String>> users, Element userElement) 384 { 385 String login = userElement.getAttribute("login"); 386 String population = userElement.getAttribute("population"); 387 388 for (Map<String, String> user : users) 389 { 390 if (user.get("login").equals(login) && user.get("populationId").equals(population)) 391 { 392 return true; 393 } 394 } 395 396 // No corresponding group has been found 397 return false; 398 } 399 400 private boolean _containsGroup(List<Map<String, String>> groups, Element groupElement) 401 { 402 String groupId = groupElement.getAttribute("id"); 403 String groupDirectory = groupElement.getAttribute("groupDirectory"); 404 405 for (Map<String, String> group : groups) 406 { 407 if (group.get("id").equals(groupId) && group.get("groupDirectory").equals(groupDirectory)) 408 { 409 return true; 410 } 411 } 412 413 // No corresponding group has been found 414 return false; 415 } 416 417 private <T> Map<String, Object> _modifyDefinitionFile(String definitionFileName, T dataToModify, BiConsumer<Document, T> modifyingConsumer) throws Exception 418 { 419 String definitionFilePath = ExtractionConstants.DEFINITIONS_DIR + definitionFileName; 420 Source definitionSrc = _sourceResolver.resolveURI(definitionFilePath); 421 File definitionFile = ((FileSource) definitionSrc).getFile(); 422 423 if (!definitionFile.exists()) 424 { 425 getLogger().error("Error while adding a description to the extraction '{}': this definition file doesn't exist.", definitionFileName); 426 return Map.of( 427 "success", false, 428 "error", "unexisting"); 429 } 430 431 String tmpFilePath = ExtractionConstants.DEFINITIONS_DIR + definitionFileName + ".tmp"; 432 Source tmpSrc = _sourceResolver.resolveURI(tmpFilePath); 433 File tmpFile = ((FileSource) tmpSrc).getFile(); 434 435 try (OutputStream os = Files.newOutputStream(Paths.get(tmpFile.getAbsolutePath()))) 436 { 437 // Parse existing definition file 438 Document document = _parseDefinitionFile(definitionFile); 439 440 // Apply the modification 441 modifyingConsumer.accept(document, dataToModify); 442 443 // Write the updated definition file 444 DOMSource source = new DOMSource(document); 445 Transformer transformer = TransformerFactory.newInstance().newTransformer(); 446 StreamResult result = new StreamResult(os); 447 transformer.transform(source, result); 448 449 Files.copy(tmpFile.toPath(), definitionFile.toPath(), StandardCopyOption.REPLACE_EXISTING); 450 451 return Map.of("success", true); 452 } 453 catch (Exception e) 454 { 455 getLogger().error("Error when trying to modify the extraction '{}'", definitionFileName, e); 456 return Map.of( 457 "success", false, 458 "error", "other-error"); 459 } 460 finally 461 { 462 // delete the temporary file to keep the original one 463 _deleteTemporaryFile(definitionFileName, tmpFile.toPath()); 464 } 465 } 466 467 private void _deleteTemporaryFile(String definitionFileName, Path temporaryFilePath) 468 { 469 try 470 { 471 Files.deleteIfExists(temporaryFilePath); 472 } 473 catch (IOException e) 474 { 475 getLogger().error("Error when deleting the temporary file for '{}'", definitionFileName, e); 476 } 477 } 478 479 /** 480 * Renames an extraction definition file. 481 * @param relativeOldFilePath The extraction definition old file path, relative to the base definitions directory 482 * @param newFileName The extraction definition new file name 483 * @return Map containing success boolean and error codes if one occurs 484 * @throws Exception if an error occurs 485 */ 486 @Callable (right = ExtractionConstants.MODIFY_EXTRACTION_RIGHT_ID) 487 public Map<String, Object> renameExtraction(String relativeOldFilePath, String newFileName) throws Exception 488 { 489 String asoluteOldFilePath = ExtractionConstants.DEFINITIONS_DIR + relativeOldFilePath; 490 Source oldSrc = _sourceResolver.resolveURI(asoluteOldFilePath); 491 File oldFile = ((FileSource) oldSrc).getFile(); 492 493 if (!oldFile.exists()) 494 { 495 getLogger().error("Error while renaming '{}': this definition file doesn't exist.", relativeOldFilePath); 496 return Map.of( 497 "success", false, 498 "error", "unexisting"); 499 } 500 501 String relativeParentPath = StringUtils.removeEnd(relativeOldFilePath, oldFile.getName()); 502 String relativeNewFilePath = relativeParentPath + newFileName; 503 String absoluteNewFilePath = ExtractionConstants.DEFINITIONS_DIR + relativeNewFilePath; 504 Source newSrc = _sourceResolver.resolveURI(absoluteNewFilePath); 505 File newFile = ((FileSource) newSrc).getFile(); 506 507 if (newFile.exists()) 508 { 509 getLogger().error("Error while renaming to '{}': a definition file with this name already exists.", relativeNewFilePath); 510 return Map.of( 511 "success", false, 512 "error", "already-exists"); 513 } 514 515 try 516 { 517 // Copy old file in the new one 518 Files.copy(oldFile.toPath(), newFile.toPath()); 519 } 520 catch (IOException e) 521 { 522 getLogger().error("Error while copying old definition file '{}' in the new one.", relativeOldFilePath, e); 523 return Map.of( 524 "success", false, 525 "error", "other-error"); 526 } 527 528 try 529 { 530 Files.deleteIfExists(oldFile.toPath()); 531 } 532 catch (IOException e) 533 { 534 getLogger().error("Error while deleting old definition file '{}'", relativeOldFilePath, e); 535 return Map.of( 536 "success", false, 537 "error", "other-error"); 538 } 539 540 return Map.of( 541 "success", true, 542 "path", relativeNewFilePath, 543 "name", newFileName); 544 } 545 546 /** 547 * Deletes an extraction definition file. 548 * @param definitionFileName The extraction definition file to delete 549 * @return <code>true</code> if extraction deletion succeed, <code>false</code> otherwise 550 * @throws Exception if an error occurs 551 */ 552 @Callable (right = ExtractionConstants.MODIFY_EXTRACTION_RIGHT_ID) 553 public boolean deleteExtraction(String definitionFileName) throws Exception 554 { 555 String filePath = ExtractionConstants.DEFINITIONS_DIR + definitionFileName; 556 Source source = _sourceResolver.resolveURI(filePath); 557 File file = ((FileSource) source).getFile(); 558 559 if (!file.exists()) 560 { 561 throw new IllegalArgumentException("Error while deleting '" + definitionFileName + "': this definition file doesn't exist."); 562 } 563 564 try 565 { 566 // Get description identifiers 567 Document document = _parseDefinitionFile(file); 568 List<String> descriptionIds = _getElements(document.getDocumentElement(), ExtractionConstants.DESCRIPTION_TAG) 569 .map(description -> description.getAttribute(ExtractionConstants.DESCRIPTION_IDENTIFIER_ATTRIBUTE_NAME)) 570 .collect(Collectors.toList()); 571 572 // Delete descriptions contents 573 _contentDAO.deleteContents(descriptionIds, ExtractionConstants.MODIFY_EXTRACTION_RIGHT_ID); 574 575 Files.deleteIfExists(file.toPath()); 576 } 577 catch (IOException e) 578 { 579 if (getLogger().isErrorEnabled()) 580 { 581 getLogger().error("Error while deleting definition file '" + definitionFileName + "'.", e); 582 } 583 throw e; 584 } 585 586 return true; 587 } 588 589 private Document _parseDefinitionFile(File definitionFile) throws Exception 590 { 591 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); 592 DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); 593 return documentBuilder.parse(definitionFile); 594 } 595 596 private Stream<Element> _getElements(Element extractionRoot, String nodeTagName) 597 { 598 NodeList nodeList = extractionRoot.getElementsByTagName(nodeTagName); 599 return IntStream.range(0, nodeList.getLength()) 600 .mapToObj(nodeList::item) 601 .map(Element.class::cast); 602 } 603 604 private Element _getOrCreateFirstElement(Document document, Element parent, String nodeTagName) 605 { 606 NodeList nodeList = parent.getElementsByTagName(nodeTagName); 607 if (nodeList.getLength() > 0) 608 { 609 return (Element) nodeList.item(0); 610 } 611 else 612 { 613 Element element = document.createElement(nodeTagName); 614 parent.appendChild(element); 615 return element; 616 } 617 } 618 619 private Optional<Element> _getFirstElement(Element parent, String nodeTagName) 620 { 621 NodeList nodeList = parent.getElementsByTagName(nodeTagName); 622 return nodeList.getLength() > 0 ? Optional.of((Element) nodeList.item(0)) : Optional.empty(); 623 } 624}