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