001    /**
002     * Copyright (c) 2000-2013 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.StructureXsdException;
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) throws SystemException {
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) throws SystemException {
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, SystemException {
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, SystemException {
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                    throws SystemException {
179    
180                    Element rootElement = null;
181    
182                    try {
183                            if (document != null) {
184                                    rootElement = document.getRootElement();
185                            }
186                            else {
187                                    document = SAXReaderUtil.createDocument();
188    
189                                    rootElement = document.addElement("root");
190                            }
191    
192                            Iterator<Field> itr = fields.iterator(true);
193    
194                            while (itr.hasNext()) {
195                                    Field field = itr.next();
196    
197                                    List<Node> nodes = getElementsByName(document, field.getName());
198    
199                                    for (Node node : nodes) {
200                                            document.remove(node);
201                                    }
202    
203                                    appendField(rootElement, field);
204                            }
205    
206                            return document.formattedString();
207                    }
208                    catch (IOException ioe) {
209                            throw new SystemException(ioe);
210                    }
211            }
212    
213            @Override
214            public String getXML(Fields fields) throws SystemException {
215                    return getXML(null, fields);
216            }
217    
218            public void setXMLSchema(XMLSchema xmlSchema) {
219                    _xmlSchema = xmlSchema;
220            }
221    
222            @Override
223            public String updateXMLDefaultLocale(
224                            String xml, Locale contentDefaultLocale,
225                            Locale contentNewDefaultLocale)
226                    throws SystemException {
227    
228                    try {
229                            if (LocaleUtil.equals(
230                                            contentDefaultLocale, contentNewDefaultLocale)) {
231    
232                                    return xml;
233                            }
234    
235                            Document document = SAXReaderUtil.read(xml);
236    
237                            Element rootElement = document.getRootElement();
238    
239                            Attribute availableLocalesAttribute = rootElement.attribute(
240                                    _AVAILABLE_LOCALES);
241    
242                            String contentNewDefaultLanguageId = LocaleUtil.toLanguageId(
243                                    contentNewDefaultLocale);
244    
245                            String availableLocalesAttributeValue =
246                                    availableLocalesAttribute.getValue();
247    
248                            if (!availableLocalesAttributeValue.contains(
249                                            contentNewDefaultLanguageId)) {
250    
251                                    StringBundler sb = new StringBundler(3);
252    
253                                    sb.append(availableLocalesAttribute.getValue());
254                                    sb.append(StringPool.COMMA);
255                                    sb.append(contentNewDefaultLanguageId);
256    
257                                    availableLocalesAttribute.setValue(sb.toString());
258                            }
259    
260                            Attribute defaultLocaleAttribute = rootElement.attribute(
261                                    _DEFAULT_LOCALE);
262    
263                            defaultLocaleAttribute.setValue(contentNewDefaultLanguageId);
264    
265                            fixElementsDefaultLocale(
266                                    rootElement, contentDefaultLocale, contentNewDefaultLocale);
267    
268                            return document.formattedString();
269                    }
270                    catch (DocumentException de) {
271                            throw new SystemException(de);
272                    }
273                    catch (IOException ioe) {
274                            throw new SystemException(ioe);
275                    }
276            }
277    
278            @Override
279            public String validateXML(String xml) throws PortalException {
280                    try {
281                            Document document = SAXReaderUtil.read(xml, _xmlSchema);
282    
283                            return document.asXML();
284                    }
285                    catch (Exception e) {
286                            if (_log.isDebugEnabled()) {
287                                    _log.debug("Invalid XML content " + e.getMessage(), e);
288                            }
289    
290                            throw new StructureXsdException();
291                    }
292            }
293    
294            protected void appendField(Element element, Field field) {
295                    Element dynamicElementElement = element.addElement("dynamic-element");
296    
297                    dynamicElementElement.addAttribute(
298                            "default-language-id",
299                            LocaleUtil.toLanguageId(field.getDefaultLocale()));
300                    dynamicElementElement.addAttribute("name", field.getName());
301    
302                    for (Locale locale : field.getAvailableLocales()) {
303                            List<Serializable> values = field.getValues(locale);
304    
305                            for (Serializable value : values) {
306                                    Element dynamicContentElement =
307                                            dynamicElementElement.addElement("dynamic-content");
308    
309                                    dynamicContentElement.addAttribute(
310                                            "language-id", LocaleUtil.toLanguageId(locale));
311    
312                                    updateField(dynamicContentElement, value);
313                            }
314                    }
315            }
316    
317            protected void fixElementsDefaultLocale(
318                    Element element, Locale contentDefaultLocale,
319                    Locale contentNewDefaultLocale) {
320    
321                    for (Element dynamicElementElement :
322                                    element.elements(_DYNAMIC_ELEMENT)) {
323    
324                            Element importMetaDataElement =
325                                    (Element)dynamicElementElement.selectSingleNode(
326                                            "meta-data[@locale='" + contentNewDefaultLocale.toString() +
327                                                    "']");
328    
329                            if (importMetaDataElement == null) {
330                                    Element metaDataElement =
331                                            (Element)dynamicElementElement.selectSingleNode(
332                                                    "meta-data[@locale='" +
333                                                            contentDefaultLocale.toString() + "']");
334    
335                                    Element copiedMetadataElement = metaDataElement.createCopy();
336    
337                                    Attribute localeAttribute = copiedMetadataElement.attribute(
338                                            _LOCALE);
339    
340                                    String contentNewDefaultLanguageId = LocaleUtil.toLanguageId(
341                                            contentNewDefaultLocale);
342    
343                                    localeAttribute.setValue(contentNewDefaultLanguageId);
344    
345                                    dynamicElementElement.add(copiedMetadataElement);
346                            }
347    
348                            fixElementsDefaultLocale(
349                                    dynamicElementElement, contentDefaultLocale,
350                                    contentNewDefaultLocale);
351                    }
352            }
353    
354            protected List<Node> getElementsByName(Document document, String name) {
355                    name = HtmlUtil.escapeXPathAttribute(name);
356    
357                    XPath xPathSelector = SAXReaderUtil.createXPath(
358                            "//dynamic-element[@name=".concat(name).concat("]"));
359    
360                    return xPathSelector.selectNodes(document);
361            }
362    
363            protected void updateField(
364                    Element dynamicContentElement, Serializable fieldValue) {
365    
366                    dynamicContentElement.clearContent();
367    
368                    if (fieldValue instanceof Date) {
369                            Date valueDate = (Date)fieldValue;
370    
371                            fieldValue = valueDate.getTime();
372                    }
373    
374                    String valueString = String.valueOf(fieldValue);
375    
376                    dynamicContentElement.addCDATA(valueString.trim());
377            }
378    
379            private static final String _AVAILABLE_LOCALES = "available-locales";
380    
381            private static final String _DEFAULT_LOCALE = "default-locale";
382    
383            private static final String _DYNAMIC_ELEMENT = "dynamic-element";
384    
385            private static final String _LOCALE = "locale";
386    
387            private static final String _XML_INDENT = "  ";
388    
389            private static Log _log = LogFactoryUtil.getLog(DDMXMLImpl.class);
390    
391            private XMLSchema _xmlSchema;
392    
393    }