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                            return null;
110                    }
111    
112                    if ((xPath != null) && !xPath.booleanValueOf(document)) {
113                            return null;
114                    }
115    
116                    Fields fields = new Fields();
117    
118                    Element rootElement = document.getRootElement();
119    
120                    List<Element> dynamicElementElements = rootElement.elements(
121                            "dynamic-element");
122    
123                    for (Element dynamicElementElement : dynamicElementElements) {
124                            String fieldName = dynamicElementElement.attributeValue("name");
125    
126                            if (!structure.hasField(fieldName) ||
127                                    ((fieldNames != null) && !fieldNames.contains(fieldName))) {
128    
129                                    continue;
130                            }
131    
132                            String fieldDataType = structure.getFieldDataType(fieldName);
133    
134                            List<Element> dynamicContentElements =
135                                    dynamicElementElement.elements("dynamic-content");
136    
137                            for (Element dynamicContentElement : dynamicContentElements) {
138                                    String fieldValue = dynamicContentElement.getText();
139    
140                                    String languageId = dynamicContentElement.attributeValue(
141                                            "language-id");
142    
143                                    Locale locale = LocaleUtil.fromLanguageId(languageId);
144    
145                                    Serializable fieldValueSerializable =
146                                            FieldConstants.getSerializable(fieldDataType, fieldValue);
147    
148                                    Field field = fields.get(fieldName);
149    
150                                    if (field == null) {
151                                            field = new Field();
152    
153                                            String defaultLanguageId =
154                                                    dynamicElementElement.attributeValue(
155                                                            "default-language-id");
156    
157                                            if (Validator.isNull(defaultLanguageId)) {
158                                                    defaultLanguageId = rootElement.attributeValue(
159                                                            "default-locale");
160                                            }
161    
162                                            Locale defaultLocale = LocaleUtil.fromLanguageId(
163                                                    defaultLanguageId);
164    
165                                            field.setDefaultLocale(defaultLocale);
166    
167                                            field.setDDMStructureId(structure.getStructureId());
168                                            field.setName(fieldName);
169                                            field.setValue(locale, fieldValueSerializable);
170    
171                                            fields.put(field);
172                                    }
173                                    else {
174                                            field.addValue(locale, fieldValueSerializable);
175                                    }
176                            }
177                    }
178    
179                    return fields;
180            }
181    
182            @Override
183            public String getXML(Document document, Fields fields)
184                    throws SystemException {
185    
186                    Element rootElement = null;
187    
188                    try {
189                            if (document != null) {
190                                    rootElement = document.getRootElement();
191                            }
192                            else {
193                                    document = SAXReaderUtil.createDocument();
194    
195                                    rootElement = document.addElement("root");
196                            }
197    
198                            Iterator<Field> itr = fields.iterator(true);
199    
200                            while (itr.hasNext()) {
201                                    Field field = itr.next();
202    
203                                    List<Node> nodes = getElementsByName(document, field.getName());
204    
205                                    for (Node node : nodes) {
206                                            document.remove(node);
207                                    }
208    
209                                    appendField(rootElement, field);
210                            }
211    
212                            return document.formattedString();
213                    }
214                    catch (IOException ioe) {
215                            throw new SystemException(ioe);
216                    }
217            }
218    
219            @Override
220            public String getXML(Fields fields) throws SystemException {
221                    return getXML(null, fields);
222            }
223    
224            public void setXMLSchema(XMLSchema xmlSchema) {
225                    _xmlSchema = xmlSchema;
226            }
227    
228            @Override
229            public String updateXMLDefaultLocale(
230                            String xml, Locale contentDefaultLocale,
231                            Locale contentNewDefaultLocale)
232                    throws SystemException {
233    
234                    try {
235                            if (LocaleUtil.equals(
236                                            contentDefaultLocale, contentNewDefaultLocale)) {
237    
238                                    return xml;
239                            }
240    
241                            Document document = SAXReaderUtil.read(xml);
242    
243                            Element rootElement = document.getRootElement();
244    
245                            Attribute availableLocalesAttribute = rootElement.attribute(
246                                    _AVAILABLE_LOCALES);
247    
248                            String contentNewDefaultLanguageId = LocaleUtil.toLanguageId(
249                                    contentNewDefaultLocale);
250    
251                            String availableLocalesAttributeValue =
252                                    availableLocalesAttribute.getValue();
253    
254                            if (!availableLocalesAttributeValue.contains(
255                                            contentNewDefaultLanguageId)) {
256    
257                                    StringBundler sb = new StringBundler(3);
258    
259                                    sb.append(availableLocalesAttribute.getValue());
260                                    sb.append(StringPool.COMMA);
261                                    sb.append(contentNewDefaultLanguageId);
262    
263                                    availableLocalesAttribute.setValue(sb.toString());
264                            }
265    
266                            Attribute defaultLocaleAttribute = rootElement.attribute(
267                                    _DEFAULT_LOCALE);
268    
269                            defaultLocaleAttribute.setValue(contentNewDefaultLanguageId);
270    
271                            fixElementsDefaultLocale(
272                                    rootElement, contentDefaultLocale, contentNewDefaultLocale);
273    
274                            return document.formattedString();
275                    }
276                    catch (DocumentException de) {
277                            throw new SystemException(de);
278                    }
279                    catch (IOException ioe) {
280                            throw new SystemException(ioe);
281                    }
282            }
283    
284            @Override
285            public String validateXML(String xml) throws PortalException {
286                    try {
287                            Document document = SAXReaderUtil.read(xml, _xmlSchema);
288    
289                            return document.asXML();
290                    }
291                    catch (Exception e) {
292                            if (_log.isDebugEnabled()) {
293                                    _log.debug("Invalid XML content " + e.getMessage(), e);
294                            }
295    
296                            throw new StructureXsdException();
297                    }
298            }
299    
300            protected void appendField(Element element, Field field) {
301                    Element dynamicElementElement = element.addElement("dynamic-element");
302    
303                    dynamicElementElement.addAttribute(
304                            "default-language-id",
305                            LocaleUtil.toLanguageId(field.getDefaultLocale()));
306                    dynamicElementElement.addAttribute("name", field.getName());
307    
308                    for (Locale locale : field.getAvailableLocales()) {
309                            List<Serializable> values = field.getValues(locale);
310    
311                            for (Serializable value : values) {
312                                    Element dynamicContentElement =
313                                            dynamicElementElement.addElement("dynamic-content");
314    
315                                    dynamicContentElement.addAttribute(
316                                            "language-id", LocaleUtil.toLanguageId(locale));
317    
318                                    updateField(dynamicContentElement, value);
319                            }
320                    }
321            }
322    
323            protected void fixElementsDefaultLocale(
324                    Element element, Locale contentDefaultLocale,
325                    Locale contentNewDefaultLocale) {
326    
327                    for (Element dynamicElementElement :
328                                    element.elements(_DYNAMIC_ELEMENT)) {
329    
330                            Element importMetaDataElement =
331                                    (Element)dynamicElementElement.selectSingleNode(
332                                            "meta-data[@locale='" + contentNewDefaultLocale.toString() +
333                                                    "']");
334    
335                            if (importMetaDataElement == null) {
336                                    Element metaDataElement =
337                                            (Element)dynamicElementElement.selectSingleNode(
338                                                    "meta-data[@locale='" +
339                                                            contentDefaultLocale.toString() + "']");
340    
341                                    Element copiedMetadataElement = metaDataElement.createCopy();
342    
343                                    Attribute localeAttribute = copiedMetadataElement.attribute(
344                                            _LOCALE);
345    
346                                    String contentNewDefaultLanguageId = LocaleUtil.toLanguageId(
347                                            contentNewDefaultLocale);
348    
349                                    localeAttribute.setValue(contentNewDefaultLanguageId);
350    
351                                    dynamicElementElement.add(copiedMetadataElement);
352                            }
353    
354                            fixElementsDefaultLocale(
355                                    dynamicElementElement, contentDefaultLocale,
356                                    contentNewDefaultLocale);
357                    }
358            }
359    
360            protected List<Node> getElementsByName(Document document, String name) {
361                    name = HtmlUtil.escapeXPathAttribute(name);
362    
363                    XPath xPathSelector = SAXReaderUtil.createXPath(
364                            "//dynamic-element[@name=".concat(name).concat("]"));
365    
366                    return xPathSelector.selectNodes(document);
367            }
368    
369            protected void updateField(
370                    Element dynamicContentElement, Serializable fieldValue) {
371    
372                    dynamicContentElement.clearContent();
373    
374                    if (fieldValue instanceof Date) {
375                            Date valueDate = (Date)fieldValue;
376    
377                            fieldValue = valueDate.getTime();
378                    }
379    
380                    String valueString = String.valueOf(fieldValue);
381    
382                    dynamicContentElement.addCDATA(valueString.trim());
383            }
384    
385            private static final String _AVAILABLE_LOCALES = "available-locales";
386    
387            private static final String _DEFAULT_LOCALE = "default-locale";
388    
389            private static final String _DYNAMIC_ELEMENT = "dynamic-element";
390    
391            private static final String _LOCALE = "locale";
392    
393            private static final String _XML_INDENT = "  ";
394    
395            private static Log _log = LogFactoryUtil.getLog(DDMXMLImpl.class);
396    
397            private XMLSchema _xmlSchema;
398    
399    }