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.storage;
016    
017    import com.liferay.portal.kernel.util.ArrayUtil;
018    import com.liferay.portal.kernel.util.HtmlUtil;
019    import com.liferay.portal.kernel.util.OrderByComparator;
020    import com.liferay.portal.kernel.util.StringBundler;
021    import com.liferay.portal.kernel.util.StringPool;
022    import com.liferay.portal.kernel.xml.Document;
023    import com.liferay.portal.kernel.xml.Element;
024    import com.liferay.portal.kernel.xml.Node;
025    import com.liferay.portal.kernel.xml.SAXReaderUtil;
026    import com.liferay.portal.kernel.xml.XPath;
027    import com.liferay.portal.service.ServiceContext;
028    import com.liferay.portal.util.PortalUtil;
029    import com.liferay.portlet.dynamicdatamapping.model.DDMContent;
030    import com.liferay.portlet.dynamicdatamapping.model.DDMStorageLink;
031    import com.liferay.portlet.dynamicdatamapping.model.DDMStructure;
032    import com.liferay.portlet.dynamicdatamapping.service.DDMContentLocalServiceUtil;
033    import com.liferay.portlet.dynamicdatamapping.service.DDMStorageLinkLocalServiceUtil;
034    import com.liferay.portlet.dynamicdatamapping.service.DDMStructureLocalServiceUtil;
035    import com.liferay.portlet.dynamicdatamapping.storage.query.ComparisonOperator;
036    import com.liferay.portlet.dynamicdatamapping.storage.query.Condition;
037    import com.liferay.portlet.dynamicdatamapping.storage.query.FieldCondition;
038    import com.liferay.portlet.dynamicdatamapping.storage.query.FieldConditionImpl;
039    import com.liferay.portlet.dynamicdatamapping.storage.query.Junction;
040    import com.liferay.portlet.dynamicdatamapping.storage.query.LogicalOperator;
041    
042    import java.io.Serializable;
043    
044    import java.util.ArrayList;
045    import java.util.Collections;
046    import java.util.Date;
047    import java.util.HashMap;
048    import java.util.Iterator;
049    import java.util.List;
050    import java.util.Map;
051    
052    /**
053     * @author Eduardo Lundgren
054     * @author Brian Wing Shun Chan
055     */
056    public class XMLStorageAdapter extends BaseStorageAdapter {
057    
058            @Override
059            protected long doCreate(
060                            long companyId, long ddmStructureId, Fields fields,
061                            ServiceContext serviceContext)
062                    throws Exception {
063    
064                    long classNameId = PortalUtil.getClassNameId(
065                            DDMContent.class.getName());
066    
067                    Document document = SAXReaderUtil.createDocument();
068    
069                    Element rootElement = document.addElement("root");
070    
071                    Iterator<Field> itr = fields.iterator();
072    
073                    while (itr.hasNext()) {
074                            Field field = itr.next();
075    
076                            Object value = field.getValue();
077    
078                            if (value instanceof Date) {
079                                    Date valueDate = (Date)value;
080    
081                                    value = valueDate.getTime();
082                            }
083    
084                            String valueString = String.valueOf(value);
085    
086                            if (valueString != null) {
087                                    valueString = valueString.trim();
088                            }
089    
090                            _appendField(rootElement, field.getName(), valueString);
091                    }
092    
093                    DDMContent ddmContent = DDMContentLocalServiceUtil.addContent(
094                            serviceContext.getUserId(), serviceContext.getScopeGroupId(),
095                            DDMStorageLink.class.getName(), null, document.formattedString(),
096                            serviceContext);
097    
098                    DDMStorageLinkLocalServiceUtil.addStorageLink(
099                            classNameId, ddmContent.getPrimaryKey(), ddmStructureId,
100                            serviceContext);
101    
102                    return ddmContent.getPrimaryKey();
103            }
104    
105            @Override
106            protected void doDeleteByClass(long classPK) throws Exception {
107                    DDMContentLocalServiceUtil.deleteDDMContent(classPK);
108    
109                    DDMStorageLinkLocalServiceUtil.deleteClassStorageLink(classPK);
110            }
111    
112            @Override
113            protected void doDeleteByDDMStructure(long ddmStructureId)
114                    throws Exception {
115    
116                    List<DDMStorageLink> ddmStorageLinks =
117                            DDMStorageLinkLocalServiceUtil.getStructureStorageLinks(
118                                    ddmStructureId);
119    
120                    for (DDMStorageLink ddmStorageLink : ddmStorageLinks) {
121                            DDMContentLocalServiceUtil.deleteDDMContent(
122                                    ddmStorageLink.getClassPK());
123                    }
124    
125                    DDMStorageLinkLocalServiceUtil.deleteStructureStorageLinks(
126                            ddmStructureId);
127            }
128    
129            @Override
130            protected List<Fields> doGetFieldsListByClasses(
131                            long ddmStructureId, long[] classPKs, List<String> fieldNames,
132                            OrderByComparator orderByComparator)
133                    throws Exception {
134    
135                    return _doQuery(
136                            ddmStructureId, classPKs, fieldNames, null, orderByComparator);
137            }
138    
139            @Override
140            protected List<Fields> doGetFieldsListByDDMStructure(
141                            long ddmStructureId, List<String> fieldNames,
142                            OrderByComparator orderByComparator)
143                    throws Exception {
144    
145                    return _doQuery(ddmStructureId, fieldNames, null, orderByComparator);
146            }
147    
148            @Override
149            protected Map<Long, Fields> doGetFieldsMapByClasses(
150                            long ddmStructureId, long[] classPKs, List<String> fieldNames)
151                    throws Exception {
152    
153                    return _doQuery(ddmStructureId, classPKs, fieldNames);
154            }
155    
156            @Override
157            protected List<Fields> doQuery(
158                            long ddmStructureId, List<String> fieldNames, Condition condition,
159                            OrderByComparator orderByComparator)
160                    throws Exception {
161    
162                    return _doQuery(
163                            ddmStructureId, fieldNames, condition, orderByComparator);
164            }
165    
166            @Override
167            protected int doQueryCount(long ddmStructureId, Condition condition)
168                    throws Exception {
169    
170                    XPath conditionXPath = null;
171    
172                    if (condition != null) {
173                            conditionXPath = _parseCondition(condition);
174                    }
175    
176                    int count = 0;
177    
178                    long[] classPKs = _getStructureClassPKs(ddmStructureId);
179    
180                    for (long classPK : classPKs) {
181                            DDMContent ddmContent = DDMContentLocalServiceUtil.getContent(
182                                    classPK);
183    
184                            Document document = SAXReaderUtil.read(ddmContent.getXml());
185    
186                            if ((conditionXPath == null) ||
187                                    ((conditionXPath != null) &&
188                                     conditionXPath.booleanValueOf(document))) {
189    
190                                    count++;
191                            }
192                    }
193    
194                    return count;
195            }
196    
197            @Override
198            protected void doUpdate(
199                            long classPK, Fields fields, boolean mergeFields,
200                            ServiceContext serviceContext)
201                    throws Exception {
202    
203                    DDMContent ddmContent = DDMContentLocalServiceUtil.getContent(classPK);
204    
205                    Document document = null;
206    
207                    Element rootElement = null;
208    
209                    if (mergeFields) {
210                            document = SAXReaderUtil.read(ddmContent.getXml());
211    
212                            rootElement = document.getRootElement();
213                    }
214                    else {
215                            document = SAXReaderUtil.createDocument();
216    
217                            rootElement = document.addElement("root");
218                    }
219    
220                    Iterator<Field> itr = fields.iterator();
221    
222                    while (itr.hasNext()) {
223                            Field field = itr.next();
224    
225                            Object value = field.getValue();
226    
227                            if (value instanceof Date) {
228                                    Date valueDate = (Date)value;
229    
230                                    value = valueDate.getTime();
231                            }
232    
233                            String fieldName = field.getName();
234                            String fieldValue = String.valueOf(value);
235    
236                            Element dynamicElementElement = _getElementByName(
237                                    document, fieldName);
238    
239                            if (dynamicElementElement == null) {
240                                    _appendField(rootElement, fieldName, fieldValue);
241                            }
242                            else {
243                                    _updateField(dynamicElementElement, fieldName, fieldValue);
244                            }
245                    }
246    
247                    ddmContent.setModifiedDate(serviceContext.getModifiedDate(null));
248                    ddmContent.setXml(document.formattedString());
249    
250                    DDMContentLocalServiceUtil.updateContent(
251                            ddmContent.getPrimaryKey(), ddmContent.getName(),
252                            ddmContent.getDescription(), ddmContent.getXml(), serviceContext);
253            }
254    
255            private Element _appendField(
256                    Element rootElement, String fieldName, String fieldValue) {
257    
258                    Element dynamicElementElement = rootElement.addElement(
259                            "dynamic-element");
260    
261                    dynamicElementElement.addElement("dynamic-content");
262    
263                    _updateField(dynamicElementElement, fieldName, fieldValue);
264    
265                    return dynamicElementElement;
266            }
267    
268            private List<Fields> _doQuery(
269                            long ddmStructureId, List<String> fieldNames, Condition condition,
270                            OrderByComparator orderByComparator)
271                    throws Exception {
272    
273                    return _doQuery(
274                            ddmStructureId, _getStructureClassPKs(ddmStructureId), fieldNames,
275                            condition, orderByComparator);
276            }
277    
278            private Map<Long, Fields> _doQuery(
279                            long ddmStructureId, long[] classPKs, List<String> fieldNames)
280                    throws Exception {
281    
282                    Map<Long, Fields> fieldsMap = new HashMap<Long, Fields>();
283    
284                    List<Fields> fieldsList = _doQuery(
285                            ddmStructureId, classPKs, fieldNames, null, null);
286    
287                    for (int i = 0; i < fieldsList.size(); i++) {
288                            Fields fields = fieldsList.get(i);
289    
290                            fieldsMap.put(classPKs[i], fields);
291                    }
292    
293                    return fieldsMap;
294            }
295    
296            private List<Fields> _doQuery(
297                            long ddmStructureId, long[] classPKs, List<String> fieldNames,
298                            Condition condition, OrderByComparator orderByComparator)
299                    throws Exception {
300    
301                    List<Fields> fieldsList = new ArrayList<Fields>();
302    
303                    XPath conditionXPath = null;
304    
305                    if (condition != null) {
306                            conditionXPath = _parseCondition(condition);
307                    }
308    
309                    DDMStructure ddmStructure =
310                            DDMStructureLocalServiceUtil.getDDMStructure(ddmStructureId);
311    
312                    for (long classPK : classPKs) {
313                            DDMContent ddmContent = DDMContentLocalServiceUtil.getContent(
314                                    classPK);
315    
316                            Document document = SAXReaderUtil.read(ddmContent.getXml());
317    
318                            if ((conditionXPath != null) &&
319                                    !conditionXPath.booleanValueOf(document)) {
320    
321                                    continue;
322                            }
323    
324                            Fields fields = new Fields();
325    
326                            Element rootElement = document.getRootElement();
327    
328                            List<Element> dynamicElementElements = rootElement.elements(
329                                    "dynamic-element");
330    
331                            for (Element dynamicElementElement : dynamicElementElements) {
332                                    String fieldName = dynamicElementElement.attributeValue("name");
333                                    String fieldValue = dynamicElementElement.elementText(
334                                            "dynamic-content");
335    
336                                    if (!ddmStructure.hasField(fieldName) ||
337                                            ((fieldNames != null) && !fieldNames.contains(fieldName))) {
338    
339                                            continue;
340                                    }
341    
342                                    String fieldDataType = ddmStructure.getFieldDataType(fieldName);
343    
344                                    Serializable fieldValueSerializable =
345                                            FieldConstants.getSerializable(fieldDataType, fieldValue);
346    
347                                    Field field = new Field(
348                                            ddmStructureId, fieldName, fieldValueSerializable);
349    
350                                    fields.put(field);
351                            }
352    
353                            fieldsList.add(fields);
354                    }
355    
356                    if (orderByComparator != null) {
357                            Collections.sort(fieldsList, orderByComparator);
358                    }
359    
360                    return fieldsList;
361            }
362    
363            private Element _getElementByName(Document document, String name) {
364                    name = HtmlUtil.escapeXPathAttribute(name);
365    
366                    XPath xPathSelector = SAXReaderUtil.createXPath(
367                            "//dynamic-element[@name=".concat(name).concat("]"));
368    
369                    List<Node> nodes = xPathSelector.selectNodes(document);
370    
371                    if (nodes.size() == 1) {
372                            return (Element)nodes.get(0);
373                    }
374                    else {
375                            return null;
376                    }
377            }
378    
379            private long[] _getStructureClassPKs(long ddmStructureId) throws Exception {
380                    List<Long> classPKs = new ArrayList<Long>();
381    
382                    List<DDMStorageLink> ddmStorageLinks =
383                            DDMStorageLinkLocalServiceUtil.getStructureStorageLinks(
384                                    ddmStructureId);
385    
386                    for (DDMStorageLink ddmStorageLink : ddmStorageLinks) {
387                            classPKs.add(ddmStorageLink.getClassPK());
388                    }
389    
390                    return ArrayUtil.toArray(classPKs.toArray(new Long[classPKs.size()]));
391            }
392    
393            private XPath _parseCondition(Condition condition) {
394                    StringBundler sb = new StringBundler(4);
395    
396                    sb.append("//dynamic-element");
397                    sb.append(StringPool.OPEN_BRACKET);
398                    sb.append(_toXPath(condition));
399                    sb.append(StringPool.CLOSE_BRACKET);
400    
401                    return SAXReaderUtil.createXPath(sb.toString());
402            }
403    
404            private String _toXPath(Condition condition) {
405                    StringBundler sb = new StringBundler();
406    
407                    if (condition.isJunction()) {
408                            sb.append(StringPool.OPEN_PARENTHESIS);
409                            sb.append(_toXPath((Junction)condition));
410                            sb.append(StringPool.CLOSE_PARENTHESIS);
411                    }
412                    else {
413                            sb.append(_toXPath((FieldConditionImpl)condition));
414                    }
415    
416                    return sb.toString();
417            }
418    
419            private String _toXPath(FieldCondition fieldCondition) {
420                    StringBundler sb = new StringBundler(6);
421    
422                    sb.append("(@name=");
423    
424                    String name = HtmlUtil.escapeXPathAttribute(
425                            String.valueOf(fieldCondition.getName()));
426    
427                    sb.append(name);
428    
429                    ComparisonOperator comparisonOperator =
430                            fieldCondition.getComparisonOperator();
431    
432                    if (comparisonOperator.equals(ComparisonOperator.LIKE)) {
433                            sb.append(" and matches(dynamic-content, ");
434                    }
435                    else {
436                            sb.append(" and dynamic-content= ");
437                    }
438    
439                    String value = HtmlUtil.escapeXPathAttribute(
440                            String.valueOf(fieldCondition.getValue()));
441    
442                    sb.append(value);
443    
444                    if (comparisonOperator.equals(ComparisonOperator.LIKE)) {
445                            sb.append(StringPool.CLOSE_PARENTHESIS);
446                    }
447    
448                    sb.append(StringPool.CLOSE_PARENTHESIS);
449    
450                    return sb.toString();
451            }
452    
453            private String _toXPath(Junction junction) {
454                    StringBundler sb = new StringBundler();
455    
456                    LogicalOperator logicalOperator = junction.getLogicalOperator();
457    
458                    String logicalOperatorString = logicalOperator.toString();
459    
460                    Iterator<Condition> itr = junction.iterator();
461    
462                    while (itr.hasNext()) {
463                            Condition condition = itr.next();
464    
465                            sb.append(_toXPath(condition));
466    
467                            if (itr.hasNext()) {
468                                    sb.append(StringPool.SPACE);
469                                    sb.append(logicalOperatorString.toLowerCase());
470                                    sb.append(StringPool.SPACE);
471                            }
472                    }
473    
474                    return sb.toString();
475            }
476    
477            private void _updateField(
478                    Element dynamicElementElement, String fieldName, String value) {
479    
480                    Element dynamicContentElement = dynamicElementElement.element(
481                            "dynamic-content");
482    
483                    dynamicElementElement.addAttribute("name", fieldName);
484    
485                    dynamicContentElement.clearContent();
486    
487                    dynamicContentElement.addCDATA(value);
488            }
489    
490    }