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