001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portlet.dynamicdatamapping.util;
016    
017    import com.liferay.portal.kernel.exception.PortalException;
018    import com.liferay.portal.kernel.exception.SystemException;
019    import com.liferay.portal.kernel.log.Log;
020    import com.liferay.portal.kernel.log.LogFactoryUtil;
021    import com.liferay.portal.kernel.security.pacl.DoPrivileged;
022    import com.liferay.portal.kernel.util.HtmlUtil;
023    import com.liferay.portal.kernel.util.LocaleUtil;
024    import com.liferay.portal.kernel.util.StringBundler;
025    import com.liferay.portal.kernel.util.StringPool;
026    import com.liferay.portal.kernel.util.StringUtil;
027    import com.liferay.portal.kernel.xml.Attribute;
028    import com.liferay.portal.kernel.xml.Document;
029    import com.liferay.portal.kernel.xml.DocumentException;
030    import com.liferay.portal.kernel.xml.Element;
031    import com.liferay.portal.kernel.xml.Node;
032    import com.liferay.portal.kernel.xml.SAXReaderUtil;
033    import com.liferay.portal.kernel.xml.XMLSchema;
034    import com.liferay.portal.kernel.xml.XPath;
035    import com.liferay.portlet.dynamicdatamapping.StructureDefinitionException;
036    import com.liferay.portlet.dynamicdatamapping.StructureDuplicateElementException;
037    import com.liferay.portlet.dynamicdatamapping.model.DDMStructure;
038    import com.liferay.portlet.dynamicdatamapping.model.DDMStructureConstants;
039    import com.liferay.portlet.dynamicdatamapping.storage.Field;
040    import com.liferay.portlet.dynamicdatamapping.storage.FieldConstants;
041    import com.liferay.portlet.dynamicdatamapping.storage.Fields;
042    import com.liferay.util.xml.XMLFormatter;
043    
044    import java.io.IOException;
045    import java.io.Serializable;
046    
047    import java.util.Date;
048    import java.util.HashSet;
049    import java.util.Iterator;
050    import java.util.List;
051    import java.util.Locale;
052    import java.util.Set;
053    
054    /**
055     * @author Bruno Basto
056     * @author Brian Wing Shun Chan
057     */
058    @DoPrivileged
059    public class DDMXMLImpl implements DDMXML {
060    
061            @Override
062            public String formatXML(Document document) {
063                    try {
064                            return document.formattedString(_XML_INDENT);
065                    }
066                    catch (IOException ioe) {
067                            throw new SystemException(ioe);
068                    }
069            }
070    
071            @Override
072            public String formatXML(String xml) {
073    
074                    // This is only supposed to format your xml, however, it will also
075                    // unwantingly change © and other characters like it into their
076                    // respective readable versions
077    
078                    try {
079                            xml = StringUtil.replace(xml, "&#", "[$SPECIAL_CHARACTER$]");
080                            xml = XMLFormatter.toString(xml, _XML_INDENT);
081                            xml = StringUtil.replace(xml, "[$SPECIAL_CHARACTER$]", "&#");
082    
083                            return xml;
084                    }
085                    catch (IOException ioe) {
086                            throw new SystemException(ioe);
087                    }
088                    catch (org.dom4j.DocumentException de) {
089                            throw new SystemException(de);
090                    }
091            }
092    
093            @Override
094            public Fields getFields(DDMStructure structure, String xml)
095                    throws PortalException {
096    
097                    return getFields(structure, null, xml, null);
098            }
099    
100            @Override
101            public Fields getFields(
102                            DDMStructure structure, XPath xPath, String xml,
103                            List<String> fieldNames)
104                    throws PortalException {
105    
106                    Document document = null;
107    
108                    try {
109                            document = SAXReaderUtil.read(xml);
110                    }
111                    catch (DocumentException de) {
112                            return null;
113                    }
114    
115                    if ((xPath != null) && !xPath.booleanValueOf(document)) {
116                            return null;
117                    }
118    
119                    Fields fields = new Fields();
120    
121                    Element rootElement = document.getRootElement();
122    
123                    List<Element> dynamicElementElements = rootElement.elements(
124                            "dynamic-element");
125    
126                    for (Element dynamicElementElement : dynamicElementElements) {
127                            String fieldName = dynamicElementElement.attributeValue("name");
128    
129                            if (!structure.hasField(fieldName) ||
130                                    ((fieldNames != null) && !fieldNames.contains(fieldName))) {
131    
132                                    continue;
133                            }
134    
135                            String fieldDataType = structure.getFieldDataType(fieldName);
136    
137                            List<Element> dynamicContentElements =
138                                    dynamicElementElement.elements("dynamic-content");
139    
140                            for (Element dynamicContentElement : dynamicContentElements) {
141                                    String fieldValue = dynamicContentElement.getText();
142    
143                                    String languageId = dynamicContentElement.attributeValue(
144                                            "language-id");
145    
146                                    Locale locale = LocaleUtil.fromLanguageId(languageId);
147    
148                                    Serializable fieldValueSerializable =
149                                            FieldConstants.getSerializable(fieldDataType, fieldValue);
150    
151                                    Field field = fields.get(fieldName);
152    
153                                    if (field == null) {
154                                            field = new Field();
155    
156                                            String defaultLanguageId =
157                                                    dynamicElementElement.attributeValue(
158                                                            "default-language-id");
159    
160                                            Locale defaultLocale = LocaleUtil.fromLanguageId(
161                                                    defaultLanguageId);
162    
163                                            field.setDefaultLocale(defaultLocale);
164    
165                                            field.setDDMStructureId(structure.getStructureId());
166                                            field.setName(fieldName);
167                                            field.setValue(locale, fieldValueSerializable);
168    
169                                            fields.put(field);
170                                    }
171                                    else {
172                                            field.addValue(locale, fieldValueSerializable);
173                                    }
174                            }
175                    }
176    
177                    return fields;
178            }
179    
180            @Override
181            public String getXML(Document document, Fields fields) {
182                    Element rootElement = null;
183    
184                    try {
185                            if (document != null) {
186                                    rootElement = document.getRootElement();
187                            }
188                            else {
189                                    document = SAXReaderUtil.createDocument();
190    
191                                    rootElement = document.addElement("root");
192                            }
193    
194                            Iterator<Field> itr = fields.iterator(true);
195    
196                            while (itr.hasNext()) {
197                                    Field field = itr.next();
198    
199                                    List<Node> nodes = getElementsByName(document, field.getName());
200    
201                                    for (Node node : nodes) {
202                                            document.remove(node);
203                                    }
204    
205                                    appendField(rootElement, field);
206                            }
207    
208                            return document.formattedString();
209                    }
210                    catch (IOException ioe) {
211                            throw new SystemException(ioe);
212                    }
213            }
214    
215            @Override
216            public String getXML(Fields fields) {
217                    return getXML(null, fields);
218            }
219    
220            public void setXMLSchema(XMLSchema xmlSchema) {
221                    _xmlSchema = xmlSchema;
222            }
223    
224            @Override
225            public String updateXMLDefaultLocale(
226                    String xml, Locale contentDefaultLocale,
227                    Locale contentNewDefaultLocale) {
228    
229                    try {
230                            if (LocaleUtil.equals(
231                                            contentDefaultLocale, contentNewDefaultLocale)) {
232    
233                                    return xml;
234                            }
235    
236                            Document document = SAXReaderUtil.read(xml);
237    
238                            Element rootElement = document.getRootElement();
239    
240                            Attribute availableLocalesAttribute = rootElement.attribute(
241                                    _AVAILABLE_LOCALES);
242    
243                            String contentNewDefaultLanguageId = LocaleUtil.toLanguageId(
244                                    contentNewDefaultLocale);
245    
246                            String availableLocalesAttributeValue =
247                                    availableLocalesAttribute.getValue();
248    
249                            if (!availableLocalesAttributeValue.contains(
250                                            contentNewDefaultLanguageId)) {
251    
252                                    StringBundler sb = new StringBundler(3);
253    
254                                    sb.append(availableLocalesAttribute.getValue());
255                                    sb.append(StringPool.COMMA);
256                                    sb.append(contentNewDefaultLanguageId);
257    
258                                    availableLocalesAttribute.setValue(sb.toString());
259                            }
260    
261                            Attribute defaultLocaleAttribute = rootElement.attribute(
262                                    _DEFAULT_LOCALE);
263    
264                            defaultLocaleAttribute.setValue(contentNewDefaultLanguageId);
265    
266                            fixElementsDefaultLocale(
267                                    rootElement, contentDefaultLocale, contentNewDefaultLocale);
268    
269                            return document.formattedString();
270                    }
271                    catch (DocumentException de) {
272                            throw new SystemException(de);
273                    }
274                    catch (IOException ioe) {
275                            throw new SystemException(ioe);
276                    }
277            }
278    
279            @Override
280            public String validateXML(String xml) throws PortalException {
281                    try {
282                            Document document = SAXReaderUtil.read(xml, _xmlSchema);
283    
284                            validate(document);
285    
286                            return document.asXML();
287                    }
288                    catch (StructureDefinitionException sde) {
289                            throw sde;
290                    }
291                    catch (StructureDuplicateElementException sdee) {
292                            throw sdee;
293                    }
294                    catch (Exception e) {
295                            if (_log.isDebugEnabled()) {
296                                    _log.debug("Invalid XML content " + e.getMessage(), e);
297                            }
298    
299                            throw new StructureDefinitionException();
300                    }
301            }
302    
303            protected void appendField(Element element, Field field) {
304                    Element dynamicElementElement = element.addElement("dynamic-element");
305    
306                    dynamicElementElement.addAttribute(
307                            "default-language-id",
308                            LocaleUtil.toLanguageId(field.getDefaultLocale()));
309                    dynamicElementElement.addAttribute("name", field.getName());
310    
311                    for (Locale locale : field.getAvailableLocales()) {
312                            List<Serializable> values = field.getValues(locale);
313    
314                            for (Serializable value : values) {
315                                    Element dynamicContentElement =
316                                            dynamicElementElement.addElement("dynamic-content");
317    
318                                    dynamicContentElement.addAttribute(
319                                            "language-id", LocaleUtil.toLanguageId(locale));
320    
321                                    updateField(dynamicContentElement, value);
322                            }
323                    }
324            }
325    
326            protected void fixElementsDefaultLocale(
327                    Element element, Locale contentDefaultLocale,
328                    Locale contentNewDefaultLocale) {
329    
330                    for (Element dynamicElementElement :
331                                    element.elements(_DYNAMIC_ELEMENT)) {
332    
333                            Element importMetaDataElement =
334                                    (Element)dynamicElementElement.selectSingleNode(
335                                            "meta-data[@locale='" + contentNewDefaultLocale.toString() +
336                                                    "']");
337    
338                            if (importMetaDataElement == null) {
339                                    Element metaDataElement =
340                                            (Element)dynamicElementElement.selectSingleNode(
341                                                    "meta-data[@locale='" +
342                                                            contentDefaultLocale.toString() + "']");
343    
344                                    Element copiedMetadataElement = metaDataElement.createCopy();
345    
346                                    Attribute localeAttribute = copiedMetadataElement.attribute(
347                                            _LOCALE);
348    
349                                    String contentNewDefaultLanguageId = LocaleUtil.toLanguageId(
350                                            contentNewDefaultLocale);
351    
352                                    localeAttribute.setValue(contentNewDefaultLanguageId);
353    
354                                    dynamicElementElement.add(copiedMetadataElement);
355                            }
356    
357                            fixElementsDefaultLocale(
358                                    dynamicElementElement, contentDefaultLocale,
359                                    contentNewDefaultLocale);
360                    }
361            }
362    
363            protected List<Node> getElementsByName(Document document, String name) {
364                    name = HtmlUtil.escapeXPathAttribute(name);
365    
366                    XPath xPathSelector = SAXReaderUtil.createXPath(
367                            "//dynamic-element[@name=".concat(name).concat("]"));
368    
369                    return xPathSelector.selectNodes(document);
370            }
371    
372            protected void updateField(
373                    Element dynamicContentElement, Serializable fieldValue) {
374    
375                    dynamicContentElement.clearContent();
376    
377                    if (fieldValue instanceof Date) {
378                            Date valueDate = (Date)fieldValue;
379    
380                            fieldValue = valueDate.getTime();
381                    }
382    
383                    String valueString = String.valueOf(fieldValue);
384    
385                    dynamicContentElement.addCDATA(valueString.trim());
386            }
387    
388            protected void validate(Document document) throws Exception {
389                    XPath xPathSelector = SAXReaderUtil.createXPath("//dynamic-element");
390    
391                    List<Node> nodes = xPathSelector.selectNodes(document);
392    
393                    Set<String> elementNames = new HashSet<String>();
394    
395                    for (Node node : nodes) {
396                            Element element = (Element)node;
397    
398                            String name = StringUtil.toLowerCase(
399                                    element.attributeValue("name"));
400    
401                            if (name.startsWith(DDMStructureConstants.XSD_NAME_RESERVED)) {
402                                    throw new StructureDefinitionException();
403                            }
404    
405                            if (elementNames.contains(name)) {
406                                    throw new StructureDuplicateElementException();
407                            }
408    
409                            elementNames.add(name);
410                    }
411            }
412    
413            private static final String _AVAILABLE_LOCALES = "available-locales";
414    
415            private static final String _DEFAULT_LOCALE = "default-locale";
416    
417            private static final String _DYNAMIC_ELEMENT = "dynamic-element";
418    
419            private static final String _LOCALE = "locale";
420    
421            private static final String _XML_INDENT = "  ";
422    
423            private static final Log _log = LogFactoryUtil.getLog(DDMXMLImpl.class);
424    
425            private XMLSchema _xmlSchema;
426    
427    }