001/* 002 * Copyright 2020 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.repository.data.extractor.xml; 017 018import java.util.HashMap; 019import java.util.List; 020import java.util.Map; 021import java.util.Optional; 022 023import org.apache.commons.lang3.StringUtils; 024import org.apache.xpath.XPathAPI; 025import org.w3c.dom.Element; 026 027import org.ametys.core.util.dom.DOMUtils; 028import org.ametys.plugins.repository.data.extractor.ModelLessValuesExtractor; 029import org.ametys.runtime.model.ModelItem; 030import org.ametys.runtime.model.type.ElementType; 031import org.ametys.runtime.model.type.ModelItemType; 032import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint; 033 034/** 035 * This class provides methods to extract values from an XML document 036 */ 037public class ModelLessXMLValuesExtractor implements ModelLessValuesExtractor 038{ 039 /** The DOM element containing the XML values */ 040 protected Element _element; 041 042 /** The getter that retrieves needed additional data by types */ 043 protected XMLValuesExtractorAdditionalDataGetter _additionalDataGetter; 044 045 /** Extension point providing available data types */ 046 protected AbstractThreadSafeComponentExtensionPoint<? extends ModelItemType> _dataTypesExtensionPoint; 047 048 /** 049 * Creates a model less XML values extractor 050 * @param element the DOM element containing the XML values 051 * @param additionalDataGetter the getter that retrieves needed additional data by types 052 * @param dataTypesExtensionPoint the extension point providing available data types 053 */ 054 public ModelLessXMLValuesExtractor(Element element, XMLValuesExtractorAdditionalDataGetter additionalDataGetter, AbstractThreadSafeComponentExtensionPoint<? extends ModelItemType> dataTypesExtensionPoint) 055 { 056 _element = element; 057 _additionalDataGetter = additionalDataGetter; 058 _dataTypesExtensionPoint = dataTypesExtensionPoint; 059 } 060 061 public Map<String, Object> extractValues() throws Exception 062 { 063 return _extractValues(_element, ""); 064 } 065 066 /** 067 * Extracts all the values in the current element 068 * @param currentElement the current element 069 * @param prefix the path of the item represented by the current element (prefix of all contained items) 070 * @return the values 071 * @throws Exception if an error occurs 072 */ 073 protected Map<String, Object> _extractValues(Element currentElement, String prefix) throws Exception 074 { 075 Map<String, Object> values = new HashMap<>(); 076 077 List<Element> children = DOMUtils.getUniqueChildElements(currentElement); 078 for (Element child : children) 079 { 080 String dataName = child.getNodeName(); 081 Object value = _extractValue(currentElement, dataName, prefix); 082 if (value != null) 083 { 084 values.put(dataName, value); 085 } 086 } 087 088 return values; 089 } 090 091 public <T> T extractValue(String dataPath) throws Exception 092 { 093 return _extractValue(_element, dataPath, ""); 094 } 095 096 /** 097 * Extracts the value at the given path 098 * @param <T> type of the value to retrieve 099 * @param currentElement the DOM element containing the items' values 100 * @param relativeDataPath The data path relative to the current element 101 * @param prefix the path of the item represented by the current element (prefix of all contained items) 102 * @return the value 103 * @throws Exception if an error occurs 104 */ 105 @SuppressWarnings("unchecked") 106 protected <T> T _extractValue(Element currentElement, String relativeDataPath, String prefix) throws Exception 107 { 108 String[] pathSegments = StringUtils.split(relativeDataPath, ModelItem.ITEM_PATH_SEPARATOR); 109 110 if (pathSegments == null || pathSegments.length < 1) 111 { 112 throw new IllegalArgumentException("Unable to extract the value of the data at the given path. This path is empty."); 113 } 114 else if (pathSegments.length == 1) 115 { 116 String dataName = relativeDataPath; 117 String dataTypeId = XPathAPI.eval(currentElement, dataName + "/@typeId").str(); 118 ModelItemType type = _dataTypesExtensionPoint.getExtension(dataTypeId); 119 120 if (type instanceof ElementType) 121 { 122 ElementType elementType = (ElementType) type; 123 String absoluteDataPath = StringUtils.isNotEmpty(prefix) ? prefix + ModelItem.ITEM_PATH_SEPARATOR + dataName : dataName; 124 Optional<Object> additionalData = _additionalDataGetter.getAdditionalData(absoluteDataPath, elementType); 125 126 return (T) elementType.valueFromXML(currentElement, dataName, additionalData); 127 } 128 else 129 { 130 String newPrefix = StringUtils.isNotEmpty(prefix) ? prefix + ModelItem.ITEM_PATH_SEPARATOR + dataName : dataName; 131 132 Element compositeNode = (Element) XPathAPI.selectSingleNode(currentElement, dataName); 133 return compositeNode != null ? (T) _extractValues(compositeNode, newPrefix) : null; 134 } 135 } 136 else 137 { 138 String firstSegmentDataName = pathSegments[0]; 139 String newRelativeDataPath = StringUtils.join(pathSegments, ModelItem.ITEM_PATH_SEPARATOR, 1, pathSegments.length); 140 String newPrefix = StringUtils.isNotEmpty(prefix) ? prefix + ModelItem.ITEM_PATH_SEPARATOR + firstSegmentDataName : firstSegmentDataName; 141 142 Element compositeNode = (Element) XPathAPI.selectSingleNode(currentElement, firstSegmentDataName); 143 return compositeNode != null ? _extractValue(compositeNode, newRelativeDataPath, newPrefix) : null; 144 } 145 } 146}