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}