001/* 002 * Copyright 2014 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.contentio.in.xml; 017 018import java.io.IOException; 019import java.util.HashSet; 020import java.util.Map; 021import java.util.Optional; 022import java.util.Set; 023import java.util.regex.Pattern; 024 025import org.apache.avalon.framework.configuration.Configuration; 026import org.apache.avalon.framework.configuration.ConfigurationException; 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.commons.lang3.StringUtils; 030import org.w3c.dom.Document; 031import org.w3c.dom.Element; 032import org.w3c.dom.Node; 033import org.w3c.dom.NodeList; 034 035import org.ametys.cms.contenttype.ContentTypesHelper; 036import org.ametys.cms.repository.Content; 037import org.ametys.cms.repository.WorkflowAwareContent; 038import org.ametys.core.util.dom.DOMUtils; 039import org.ametys.plugins.repository.data.extractor.xml.ModelAwareXMLValuesExtractor; 040 041/** 042 * Default implementation of an XML content importer. 043 */ 044public class DefaultXmlContentImporter extends AbstractXmlContentImporter 045{ 046 047 /** Default content types key in the import map. */ 048 protected static final String _DEFAULT_CONTENT_TYPES_KEY = DefaultXmlContentImporter.class.getName() + "$defaultContentTypes"; 049 /** Default content mixins key in the import map. */ 050 protected static final String _DEFAULT_CONTENT_MIXINS_KEY = DefaultXmlContentImporter.class.getName() + "$defaultMixins"; 051 /** Default content language key in the import map. */ 052 protected static final String _DEFAULT_CONTENT_LANG_KEY = DefaultXmlContentImporter.class.getName() + "$defaultLanguage"; 053 054 /** The content type helper. */ 055 protected ContentTypesHelper _cTypeHelper; 056 057 /** The XPath expression to match. */ 058 protected String _matchPath; 059 060 /** The XPath value to match. */ 061 protected String _matchValue; 062 063 /** The XPath value to match. */ 064 protected Pattern _matchRegex; 065 066 @Override 067 public void service(ServiceManager serviceManager) throws ServiceException 068 { 069 super.service(serviceManager); 070 _cTypeHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE); 071 } 072 073 @Override 074 public void configure(Configuration configuration) throws ConfigurationException 075 { 076 super.configure(configuration); 077 078 configureXmlMatch(configuration.getChild("xml").getChild("match")); 079 } 080 081 /** 082 * Configure the matching value. 083 * @param configuration the matching configuration. 084 * @throws ConfigurationException if an error occurs. 085 */ 086 protected void configureXmlMatch(Configuration configuration) throws ConfigurationException 087 { 088 _matchPath = configuration.getAttribute("path"); 089 _matchValue = configuration.getAttribute("value", null); 090 String regex = configuration.getAttribute("regex", null); 091 if (StringUtils.isNotBlank(regex)) 092 { 093 _matchRegex = Pattern.compile(regex); 094 } 095 } 096 097 @Override 098 public boolean supports(Document document) throws IOException 099 { 100 Node node = _xPathProcessor.selectSingleNode(document, _matchPath, getPrefixResolver()); 101 102 if (_matchValue == null && _matchRegex == null) 103 { 104 return node != null; 105 } 106 else if (_matchRegex != null) 107 { 108 String text = getTextContent(node, null, true); 109 return text != null ? _matchRegex.matcher(text).matches() : false; 110 } 111 else 112 { 113 return _matchValue.equals(getTextContent(node, null, true)); 114 } 115 } 116 117 @Override 118 protected Set<String> importContents(Document node, Map<String, Object> params) throws IOException 119 { 120 Set<String> contentIds = new HashSet<>(); 121 122 Element root = node.getDocumentElement(); 123 124 if (root != null) 125 { 126 String[] defaultTypes = StringUtils.split(getAttributeValue(root, "default-types", ""), ", "); 127 String[] defaultMixins = StringUtils.split(getAttributeValue(root, "default-mixins", ""), ", "); 128 String defaultLang = getAttributeValue(root, "default-language", getLanguage(params)); 129 if (defaultTypes.length == 0) 130 { 131 defaultTypes = getContentTypes(params); 132 } 133 if (defaultMixins.length == 0) 134 { 135 defaultMixins = getMixins(params); 136 } 137 138 params.put(_DEFAULT_CONTENT_TYPES_KEY, defaultTypes); 139 params.put(_DEFAULT_CONTENT_MIXINS_KEY, defaultMixins); 140 params.put(_DEFAULT_CONTENT_LANG_KEY, defaultLang); 141 142 // Import all the contents. 143 NodeList contents = _xPathProcessor.selectNodeList(root, "content", getPrefixResolver()); 144 for (int i = 0; i < contents.getLength(); i++) 145 { 146 Element contentNode = (Element) contents.item(i); 147 148 try 149 { 150 Content content = importContent(contentNode, defaultTypes, defaultMixins, defaultLang, params); 151 152 if (content != null) 153 { 154 contentIds.add(content.getId()); 155 } 156 } 157 catch (Exception e) 158 { 159 getLogger().error("Error importing a content.", e); 160 } 161 } 162 163 params.remove(_DEFAULT_CONTENT_TYPES_KEY); 164 params.remove(_DEFAULT_CONTENT_MIXINS_KEY); 165 params.remove(_DEFAULT_CONTENT_LANG_KEY); 166 } 167 168 return contentIds; 169 } 170 171 /** 172 * Import a content from a XML node. 173 * @param contentElement the content XML node. 174 * @param defaultTypes the default content types. 175 * @param defaultMixins the default mixins. 176 * @param defaultLang the default content language. 177 * @param params the import parameters. 178 * @return the Content or null if not created. 179 * @throws Exception if an error occurs creating the Content. 180 */ 181 protected Content importContent(Element contentElement, String[] defaultTypes, String[] defaultMixins, String defaultLang, Map<String, Object> params) throws Exception 182 { 183 String localId = getAttributeValue(contentElement, "id", ""); 184 185 String cTypesStr = getAttributeValue(contentElement, "types", ""); 186 String mixinsStr = getAttributeValue(contentElement, "mixins", ""); 187 188 String[] contentTypes = StringUtils.isEmpty(cTypesStr) ? defaultTypes : StringUtils.split(cTypesStr, ", "); 189 String[] contentMixins = StringUtils.isEmpty(mixinsStr) ? defaultMixins : StringUtils.split(mixinsStr, ", "); 190 String language = getAttributeValue(contentElement, "language", defaultLang); 191 192 String title = _xPathProcessor.evaluateAsString(contentElement, "@title", getPrefixResolver()); 193 194 WorkflowAwareContent content = (WorkflowAwareContent) createContent(title, contentTypes, contentMixins, language, params); 195 196 ModelAwareXMLValuesExtractor extractor = new ModelAwareXMLValuesExtractor(DOMUtils.getChildElementByTagName(contentElement, "attributes"), (path, type) -> Optional.empty(), content.getModel()); 197 Map<String, Object> values = extractor.extractValues(); 198 _contentWorkflowHelper.editContent(content, values, getEditActionId(params)); 199 200 // If the content contains a "local" ID (local to the import file), remember it. 201 if (StringUtils.isNotBlank(localId)) 202 { 203 Map<String, String> contentIdMap = getContentIdMap(params); 204 contentIdMap.put(localId, content.getId()); 205 } 206 207 return content; 208 } 209}