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.model.DDMStructure;
037    import com.liferay.portlet.dynamicdatamapping.storage.Field;
038    import com.liferay.portlet.dynamicdatamapping.storage.FieldConstants;
039    import com.liferay.portlet.dynamicdatamapping.storage.Fields;
040    import com.liferay.util.xml.XMLFormatter;
041    
042    import java.io.IOException;
043    import java.io.Serializable;
044    
045    import java.util.Date;
046    import java.util.Iterator;
047    import java.util.List;
048    import java.util.Locale;
049    
050    /**
051     * @author Bruno Basto
052     * @author Brian Wing Shun Chan
053     */
054    @DoPrivileged
055    public class DDMXMLImpl implements DDMXML {
056    
057            @Override
058            public String formatXML(Document document) {
059                    try {
060                            return document.formattedString(_XML_INDENT);
061                    }
062                    catch (IOException ioe) {
063                            throw new SystemException(ioe);
064                    }
065            }
066    
067            @Override
068            public String formatXML(String xml) {
069    
070                    // This is only supposed to format your xml, however, it will also
071                    // unwantingly change © and other characters like it into their
072                    // respective readable versions
073    
074                    try {
075                            xml = StringUtil.replace(xml, "&#", "[$SPECIAL_CHARACTER$]");
076                            xml = XMLFormatter.toString(xml, _XML_INDENT);
077                            xml = StringUtil.replace(xml, "[$SPECIAL_CHARACTER$]", "&#");
078    
079                            return xml;
080                    }
081                    catch (IOException ioe) {
082                            throw new SystemException(ioe);
083                    }
084                    catch (org.dom4j.DocumentException de) {
085                            throw new SystemException(de);
086                    }
087            }
088    
089            @Override
090            public Fields getFields(DDMStructure structure, String xml)
091                    throws PortalException {
092    
093                    return getFields(structure, null, xml, null);
094            }
095    
096            @Override
097            public Fields getFields(
098                            DDMStructure structure, XPath xPath, String xml,
099                            List<String> fieldNames)
100                    throws PortalException {
101    
102                    Document document = null;
103    
104                    try {
105                            document = SAXReaderUtil.read(xml);
106                    }
107                    catch (DocumentException de) {
108                            return null;
109                    }
110    
111                    if ((xPath != null) && !xPath.booleanValueOf(document)) {
112                            return null;
113                    }
114    
115                    Fields fields = new Fields();
116    
117                    Element rootElement = document.getRootElement();
118    
119                    List<Element> dynamicElementElements = rootElement.elements(
120                            "dynamic-element");
121    
122                    for (Element dynamicElementElement : dynamicElementElements) {
123                            String fieldName = dynamicElementElement.attributeValue("name");
124    
125                            if (!structure.hasField(fieldName) ||
126                                    ((fieldNames != null) && !fieldNames.contains(fieldName))) {
127    
128                                    continue;
129                            }
130    
131                            String fieldDataType = structure.getFieldDataType(fieldName);
132    
133                            List<Element> dynamicContentElements =
134                                    dynamicElementElement.elements("dynamic-content");
135    
136                            for (Element dynamicContentElement : dynamicContentElements) {
137                                    String fieldValue = dynamicContentElement.getText();
138    
139                                    String languageId = dynamicContentElement.attributeValue(
140                                            "language-id");
141    
142                                    Locale locale = LocaleUtil.fromLanguageId(languageId);
143    
144                                    Serializable fieldValueSerializable =
145                                            FieldConstants.getSerializable(fieldDataType, fieldValue);
146    
147                                    Field field = fields.get(fieldName);
148    
149                                    if (field == null) {
150                                            field = new Field();
151    
152                                            String defaultLanguageId =
153                                                    dynamicElementElement.attributeValue(
154                                                            "default-language-id");
155    
156                                            Locale defaultLocale = LocaleUtil.fromLanguageId(
157                                                    defaultLanguageId);
158    
159                                            field.setDefaultLocale(defaultLocale);
160    
161                                            field.setDDMStructureId(structure.getStructureId());
162                                            field.setName(fieldName);
163                                            field.setValue(locale, fieldValueSerializable);
164    
165                                            fields.put(field);
166                                    }
167                                    else {
168                                            field.addValue(locale, fieldValueSerializable);
169                                    }
170                            }
171                    }
172    
173                    return fields;
174            }
175    
176            @Override
177            public String getXML(Document document, Fields fields) {
178                    Element rootElement = null;
179    
180                    try {
181                            if (document != null) {
182                                    rootElement = document.getRootElement();
183                            }
184                            else {
185                                    document = SAXReaderUtil.createDocument();
186    
187                                    rootElement = document.addElement("root");
188                            }
189    
190                            Iterator<Field> itr = fields.iterator(true);
191    
192                            while (itr.hasNext()) {
193                                    Field field = itr.next();
194    
195                                    List<Node> nodes = getElementsByName(document, field.getName());
196    
197                                    for (Node node : nodes) {
198                                            document.remove(node);
199                                    }
200    
201                                    appendField(rootElement, field);
202                            }
203    
204                            return document.formattedString();
205                    }
206                    catch (IOException ioe) {
207                            throw new SystemException(ioe);
208                    }
209            }
210    
211            @Override
212            public String getXML(Fields fields) {
213                    return getXML(null, fields);
214            }
215    
216            public void setXMLSchema(XMLSchema xmlSchema) {
217                    _xmlSchema = xmlSchema;
218            }
219    
220            @Override
221            public String updateXMLDefaultLocale(
222                    String xml, Locale contentDefaultLocale,
223                    Locale contentNewDefaultLocale) {
224    
225                    try {
226                            if (LocaleUtil.equals(
227                                            contentDefaultLocale, contentNewDefaultLocale)) {
228    
229                                    return xml;
230                            }
231    
232                            Document document = SAXReaderUtil.read(xml);
233    
234                            Element rootElement = document.getRootElement();
235    
236                            Attribute availableLocalesAttribute = rootElement.attribute(
237                                    _AVAILABLE_LOCALES);
238    
239                            String contentNewDefaultLanguageId = LocaleUtil.toLanguageId(
240                                    contentNewDefaultLocale);
241    
242                            String availableLocalesAttributeValue =
243                                    availableLocalesAttribute.getValue();
244    
245                            if (!availableLocalesAttributeValue.contains(
246                                            contentNewDefaultLanguageId)) {
247    
248                                    StringBundler sb = new StringBundler(3);
249    
250                                    sb.append(availableLocalesAttribute.getValue());
251                                    sb.append(StringPool.COMMA);
252                                    sb.append(contentNewDefaultLanguageId);
253    
254                                    availableLocalesAttribute.setValue(sb.toString());
255                            }
256    
257                            Attribute defaultLocaleAttribute = rootElement.attribute(
258                                    _DEFAULT_LOCALE);
259    
260                            defaultLocaleAttribute.setValue(contentNewDefaultLanguageId);
261    
262                            fixElementsDefaultLocale(
263                                    rootElement, contentDefaultLocale, contentNewDefaultLocale);
264    
265                            return document.formattedString();
266                    }
267                    catch (DocumentException de) {
268                            throw new SystemException(de);
269                    }
270                    catch (IOException ioe) {
271                            throw new SystemException(ioe);
272                    }
273            }
274    
275            @Override
276            public String validateXML(String xml) throws PortalException {
277                    try {
278                            Document document = SAXReaderUtil.read(xml, _xmlSchema);
279    
280                            return document.asXML();
281                    }
282                    catch (Exception e) {
283                            if (_log.isDebugEnabled()) {
284                                    _log.debug("Invalid XML content " + e.getMessage(), e);
285                            }
286    
287                            throw new StructureDefinitionException();
288                    }
289            }
290    
291            protected void appendField(Element element, Field field) {
292                    Element dynamicElementElement = element.addElement("dynamic-element");
293    
294                    dynamicElementElement.addAttribute(
295                            "default-language-id",
296                            LocaleUtil.toLanguageId(field.getDefaultLocale()));
297                    dynamicElementElement.addAttribute("name", field.getName());
298    
299                    for (Locale locale : field.getAvailableLocales()) {
300                            List<Serializable> values = field.getValues(locale);
301    
302                            for (Serializable value : values) {
303                                    Element dynamicContentElement =
304                                            dynamicElementElement.addElement("dynamic-content");
305    
306                                    dynamicContentElement.addAttribute(
307                                            "language-id", LocaleUtil.toLanguageId(locale));
308    
309                                    updateField(dynamicContentElement, value);
310                            }
311                    }
312            }
313    
314            protected void fixElementsDefaultLocale(
315                    Element element, Locale contentDefaultLocale,
316                    Locale contentNewDefaultLocale) {
317    
318                    for (Element dynamicElementElement :
319                                    element.elements(_DYNAMIC_ELEMENT)) {
320    
321                            Element importMetaDataElement =
322                                    (Element)dynamicElementElement.selectSingleNode(
323                                            "meta-data[@locale='" + contentNewDefaultLocale.toString() +
324                                                    "']");
325    
326                            if (importMetaDataElement == null) {
327                                    Element metaDataElement =
328                                            (Element)dynamicElementElement.selectSingleNode(
329                                                    "meta-data[@locale='" +
330                                                            contentDefaultLocale.toString() + "']");
331    
332                                    Element copiedMetadataElement = metaDataElement.createCopy();
333    
334                                    Attribute localeAttribute = copiedMetadataElement.attribute(
335                                            _LOCALE);
336    
337                                    String contentNewDefaultLanguageId = LocaleUtil.toLanguageId(
338                                            contentNewDefaultLocale);
339    
340                                    localeAttribute.setValue(contentNewDefaultLanguageId);
341    
342                                    dynamicElementElement.add(copiedMetadataElement);
343                            }
344    
345                            fixElementsDefaultLocale(
346                                    dynamicElementElement, contentDefaultLocale,
347                                    contentNewDefaultLocale);
348                    }
349            }
350    
351            protected List<Node> getElementsByName(Document document, String name) {
352                    name = HtmlUtil.escapeXPathAttribute(name);
353    
354                    XPath xPathSelector = SAXReaderUtil.createXPath(
355                            "//dynamic-element[@name=".concat(name).concat("]"));
356    
357                    return xPathSelector.selectNodes(document);
358            }
359    
360            protected void updateField(
361                    Element dynamicContentElement, Serializable fieldValue) {
362    
363                    dynamicContentElement.clearContent();
364    
365                    if (fieldValue instanceof Date) {
366                            Date valueDate = (Date)fieldValue;
367    
368                            fieldValue = valueDate.getTime();
369                    }
370    
371                    String valueString = String.valueOf(fieldValue);
372    
373                    dynamicContentElement.addCDATA(valueString.trim());
374            }
375    
376            private static final String _AVAILABLE_LOCALES = "available-locales";
377    
378            private static final String _DEFAULT_LOCALE = "default-locale";
379    
380            private static final String _DYNAMIC_ELEMENT = "dynamic-element";
381    
382            private static final String _LOCALE = "locale";
383    
384            private static final String _XML_INDENT = "  ";
385    
386            private static final Log _log = LogFactoryUtil.getLog(DDMXMLImpl.class);
387    
388            private XMLSchema _xmlSchema;
389    
390    }