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.counter.service.CounterLocalServiceUtil;
018    import com.liferay.portal.kernel.exception.PortalException;
019    import com.liferay.portal.kernel.exception.SystemException;
020    import com.liferay.portal.kernel.log.Log;
021    import com.liferay.portal.kernel.log.LogFactoryUtil;
022    import com.liferay.portal.kernel.util.ArrayUtil;
023    import com.liferay.portal.kernel.util.OrderByComparator;
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.service.ServiceContext;
029    import com.liferay.portal.util.PortalUtil;
030    import com.liferay.portlet.dynamicdatamapping.model.DDMStorageLink;
031    import com.liferay.portlet.dynamicdatamapping.model.DDMStructure;
032    import com.liferay.portlet.dynamicdatamapping.service.DDMStorageLinkLocalServiceUtil;
033    import com.liferay.portlet.dynamicdatamapping.service.DDMStructureLocalServiceUtil;
034    import com.liferay.portlet.dynamicdatamapping.storage.query.ComparisonOperator;
035    import com.liferay.portlet.dynamicdatamapping.storage.query.Condition;
036    import com.liferay.portlet.dynamicdatamapping.storage.query.FieldCondition;
037    import com.liferay.portlet.dynamicdatamapping.storage.query.Junction;
038    import com.liferay.portlet.dynamicdatamapping.storage.query.LogicalOperator;
039    import com.liferay.portlet.dynamicdatamapping.util.DDMUtil;
040    import com.liferay.portlet.expando.NoSuchTableException;
041    import com.liferay.portlet.expando.model.ExpandoColumn;
042    import com.liferay.portlet.expando.model.ExpandoColumnConstants;
043    import com.liferay.portlet.expando.model.ExpandoRow;
044    import com.liferay.portlet.expando.model.ExpandoTable;
045    import com.liferay.portlet.expando.model.ExpandoValue;
046    import com.liferay.portlet.expando.service.ExpandoColumnLocalServiceUtil;
047    import com.liferay.portlet.expando.service.ExpandoRowLocalServiceUtil;
048    import com.liferay.portlet.expando.service.ExpandoTableLocalServiceUtil;
049    import com.liferay.portlet.expando.service.ExpandoValueLocalServiceUtil;
050    
051    import java.io.Serializable;
052    
053    import java.util.ArrayList;
054    import java.util.Collections;
055    import java.util.Date;
056    import java.util.HashMap;
057    import java.util.Iterator;
058    import java.util.List;
059    import java.util.Locale;
060    import java.util.Map;
061    
062    import org.springframework.expression.EvaluationException;
063    import org.springframework.expression.Expression;
064    import org.springframework.expression.ExpressionParser;
065    import org.springframework.expression.ParseException;
066    import org.springframework.expression.spel.standard.SpelExpressionParser;
067    import org.springframework.expression.spel.support.StandardEvaluationContext;
068    
069    /**
070     * @author Eduardo Lundgren
071     * @author Brian Wing Shun Chan
072     * @author Marcellus Tavares
073     */
074    public class ExpandoStorageAdapter extends BaseStorageAdapter {
075    
076            @Override
077            protected long doCreate(
078                            long companyId, long ddmStructureId, Fields fields,
079                            ServiceContext serviceContext)
080                    throws Exception {
081    
082                    ExpandoTable expandoTable = _getExpandoTable(
083                            companyId, ddmStructureId, fields);
084    
085                    ExpandoRow expandoRow = ExpandoRowLocalServiceUtil.addRow(
086                            expandoTable.getTableId(), CounterLocalServiceUtil.increment());
087    
088                    _updateFields(expandoTable, expandoRow.getClassPK(), fields);
089    
090                    DDMStorageLinkLocalServiceUtil.addStorageLink(
091                            expandoTable.getClassNameId(), expandoRow.getRowId(),
092                            ddmStructureId, serviceContext);
093    
094                    return expandoRow.getRowId();
095            }
096    
097            @Override
098            protected void doDeleteByClass(long classPK) throws Exception {
099                    ExpandoRowLocalServiceUtil.deleteRow(classPK);
100    
101                    DDMStorageLinkLocalServiceUtil.deleteClassStorageLink(classPK);
102            }
103    
104            @Override
105            protected void doDeleteByDDMStructure(long ddmStructureId)
106                    throws Exception {
107    
108                    List<DDMStorageLink> ddmStorageLinks =
109                            DDMStorageLinkLocalServiceUtil.getStructureStorageLinks(
110                                    ddmStructureId);
111    
112                    for (DDMStorageLink ddmStorageLink : ddmStorageLinks) {
113                            ExpandoRowLocalServiceUtil.deleteRow(ddmStorageLink.getClassPK());
114                    }
115    
116                    DDMStorageLinkLocalServiceUtil.deleteStructureStorageLinks(
117                            ddmStructureId);
118            }
119    
120            @Override
121            protected List<Fields> doGetFieldsListByClasses(
122                            long ddmStructureId, long[] classPKs, List<String> fieldNames,
123                            OrderByComparator orderByComparator)
124                    throws Exception {
125    
126                    return _doQuery(
127                            ddmStructureId, classPKs, fieldNames, null, orderByComparator);
128            }
129    
130            @Override
131            protected List<Fields> doGetFieldsListByDDMStructure(
132                            long ddmStructureId, List<String> fieldNames,
133                            OrderByComparator orderByComparator)
134                    throws Exception {
135    
136                    return _doQuery(ddmStructureId, fieldNames, null, orderByComparator);
137            }
138    
139            @Override
140            protected Map<Long, Fields> doGetFieldsMapByClasses(
141                            long ddmStructureId, long[] classPKs, List<String> fieldNames)
142                    throws Exception {
143    
144                    return _doQuery(ddmStructureId, classPKs, fieldNames);
145            }
146    
147            @Override
148            protected List<Fields> doQuery(
149                            long ddmStructureId, List<String> fieldNames, Condition condition,
150                            OrderByComparator orderByComparator)
151                    throws Exception {
152    
153                    return _doQuery(
154                            ddmStructureId, fieldNames, condition, orderByComparator);
155            }
156    
157            @Override
158            protected int doQueryCount(long ddmStructureId, Condition condition)
159                    throws Exception {
160    
161                    Expression expression = null;
162    
163                    if (condition != null) {
164                            expression = _parseExpression(condition);
165                    }
166    
167                    int count = 0;
168    
169                    long[] expandoRowIds = _getExpandoRowIds(ddmStructureId);
170    
171                    for (long expandoRowId : expandoRowIds) {
172                            List<ExpandoValue> expandoValues =
173                                    ExpandoValueLocalServiceUtil.getRowValues(expandoRowId);
174    
175                            if ((expression == null) ||
176                                    ((expression != null) &&
177                                     _booleanValueOf(expression, expandoValues))) {
178    
179                                    count++;
180                            }
181                    }
182    
183                    return count;
184            }
185    
186            @Override
187            protected void doUpdate(
188                            long classPK, Fields fields, boolean mergeFields,
189                            ServiceContext serviceContext)
190                    throws Exception {
191    
192                    ExpandoRow expandoRow = ExpandoRowLocalServiceUtil.getRow(classPK);
193    
194                    DDMStorageLink ddmStorageLink =
195                            DDMStorageLinkLocalServiceUtil.getClassStorageLink(
196                                    expandoRow.getRowId());
197    
198                    ExpandoTable expandoTable = _getExpandoTable(
199                            expandoRow.getCompanyId(), ddmStorageLink.getStructureId(), fields);
200    
201                    if (mergeFields) {
202                            fields = DDMUtil.mergeFields(fields, getFields(classPK));
203                    }
204    
205                    ExpandoValueLocalServiceUtil.deleteRowValues(expandoRow.getRowId());
206    
207                    _updateFields(expandoTable, expandoRow.getClassPK(), fields);
208            }
209    
210            private boolean _booleanValueOf(
211                    Expression expression, List<ExpandoValue> expandoValues) {
212    
213                    try {
214                            StandardEvaluationContext standardEvaluationContext =
215                                    new StandardEvaluationContext();
216    
217                            standardEvaluationContext.setBeanResolver(
218                                    new ExpandoValueBeanResolver(expandoValues));
219    
220                            return expression.getValue(
221                                    standardEvaluationContext, Boolean.class);
222                    }
223                    catch (EvaluationException ee) {
224                            _log.error("Unable to evaluate expression", ee);
225                    }
226    
227                    return false;
228            }
229    
230            private void _checkExpandoColumns(ExpandoTable expandoTable, Fields fields)
231                    throws PortalException, SystemException {
232    
233                    for (String name : fields.getNames()) {
234                            ExpandoColumn expandoColumn =
235                                    ExpandoColumnLocalServiceUtil.getColumn(
236                                            expandoTable.getTableId(), name);
237    
238                            if (expandoColumn != null) {
239                                    continue;
240                            }
241    
242                            int type = ExpandoColumnConstants.STRING_LOCALIZED;
243    
244                            Field field = fields.get(name);
245    
246                            if (field.isRepeatable()) {
247                                    type = ExpandoColumnConstants.STRING_ARRAY_LOCALIZED;
248                            }
249    
250                            ExpandoColumnLocalServiceUtil.addColumn(
251                                    expandoTable.getTableId(), name, type);
252                    }
253            }
254    
255            private List<Fields> _doQuery(
256                            long ddmStructureId, List<String> fieldNames, Condition condition,
257                            OrderByComparator orderByComparator)
258                    throws Exception {
259    
260                    return _doQuery(
261                            ddmStructureId, _getExpandoRowIds(ddmStructureId), fieldNames,
262                            condition, orderByComparator);
263            }
264    
265            private Map<Long, Fields> _doQuery(
266                            long ddmStructureId, long[] classPKs, List<String> fieldNames)
267                    throws Exception {
268    
269                    Map<Long, Fields> fieldsMap = new HashMap<Long, Fields>();
270    
271                    List<Fields> fieldsList = _doQuery(
272                            ddmStructureId, classPKs, fieldNames, null, null);
273    
274                    for (int i = 0; i < fieldsList.size(); i++) {
275                            Fields fields = fieldsList.get(i);
276    
277                            fieldsMap.put(classPKs[i], fields);
278                    }
279    
280                    return fieldsMap;
281            }
282    
283            private List<Fields> _doQuery(
284                            long ddmStructureId, long[] expandoRowIds, List<String> fieldNames,
285                            Condition condition, OrderByComparator orderByComparator)
286                    throws Exception {
287    
288                    List<Fields> fieldsList = new ArrayList<Fields>();
289    
290                    Expression expression = null;
291    
292                    if (condition != null) {
293                            expression = _parseExpression(condition);
294                    }
295    
296                    DDMStructure ddmStructure = DDMStructureLocalServiceUtil.getStructure(
297                            ddmStructureId);
298    
299                    for (long expandoRowId : expandoRowIds) {
300                            List<ExpandoValue> expandoValues =
301                                    ExpandoValueLocalServiceUtil.getRowValues(expandoRowId);
302    
303                            if ((expression == null) ||
304                                    ((expression != null) &&
305                                     _booleanValueOf(expression, expandoValues))) {
306    
307                                    Fields fields = new Fields();
308    
309                                    for (ExpandoValue expandoValue : expandoValues) {
310                                            ExpandoColumn column = expandoValue.getColumn();
311    
312                                            String fieldName = column.getName();
313    
314                                            if (ddmStructure.hasField(fieldName) &&
315                                                    ((fieldNames == null) ||
316                                                     ((fieldNames != null) &&
317                                                      fieldNames.contains(fieldName)))) {
318    
319                                                    Field field = new Field();
320    
321                                                    field.setDefaultLocale(expandoValue.getDefaultLocale());
322                                                    field.setDDMStructureId(ddmStructureId);
323                                                    field.setName(fieldName);
324    
325                                                    String fieldType = ddmStructure.getFieldDataType(
326                                                            fieldName);
327    
328                                                    Map<Locale, List<Serializable>> valuesMap =
329                                                            _getValuesMap(
330                                                                    column.getType(), fieldType,
331                                                                    expandoValue.getSerializable());
332    
333                                                    field.setValuesMap(valuesMap);
334    
335                                                    fields.put(field);
336                                            }
337                                    }
338    
339                                    fieldsList.add(fields);
340                            }
341                    }
342    
343                    if (orderByComparator != null) {
344                            Collections.sort(fieldsList, orderByComparator);
345                    }
346    
347                    return fieldsList;
348            }
349    
350            private long[] _getExpandoRowIds(long ddmStructureId)
351                    throws SystemException {
352    
353                    List<Long> expandoRowIds = new ArrayList<Long>();
354    
355                    List<DDMStorageLink> ddmStorageLinks =
356                            DDMStorageLinkLocalServiceUtil.getStructureStorageLinks(
357                                    ddmStructureId);
358    
359                    for (DDMStorageLink ddmStorageLink : ddmStorageLinks) {
360                            expandoRowIds.add(ddmStorageLink.getClassPK());
361                    }
362    
363                    return ArrayUtil.toArray(
364                            expandoRowIds.toArray(new Long[expandoRowIds.size()]));
365            }
366    
367            private ExpandoTable _getExpandoTable(
368                            long companyId, long ddmStructureId, Fields fields)
369                    throws PortalException, SystemException {
370    
371                    ExpandoTable expandoTable = null;
372    
373                    long classNameId = PortalUtil.getClassNameId(
374                            ExpandoStorageAdapter.class.getName());
375    
376                    try {
377                            expandoTable = ExpandoTableLocalServiceUtil.getTable(
378                                    companyId, classNameId, String.valueOf(ddmStructureId));
379                    }
380                    catch (NoSuchTableException nste) {
381                            expandoTable = ExpandoTableLocalServiceUtil.addTable(
382                                    companyId, classNameId, String.valueOf(ddmStructureId));
383                    }
384    
385                    _checkExpandoColumns(expandoTable, fields);
386    
387                    return expandoTable;
388            }
389    
390            private Map<Locale, List<Serializable>> _getValuesMap(
391                    int columnType, String fieldType, Serializable data) {
392    
393                    Map<Locale, List<Serializable>> valuesMap =
394                            new HashMap<Locale, List<Serializable>>();
395    
396                    if (columnType == ExpandoColumnConstants.STRING_ARRAY_LOCALIZED) {
397                            Map<Locale, String[]> stringArrayMap = (Map<Locale, String[]>)data;
398    
399                            for (Locale locale : stringArrayMap.keySet()) {
400                                    String[] value = stringArrayMap.get(locale);
401    
402                                    if (ArrayUtil.isEmpty(value)) {
403                                            continue;
404                                    }
405    
406                                    valuesMap.put(locale, _transformValue(fieldType, value));
407                            }
408                    }
409                    else {
410                            Map<Locale, String> stringMap = (Map<Locale, String>)data;
411    
412                            for (Locale locale : stringMap.keySet()) {
413                                    String value = stringMap.get(locale);
414    
415                                    if (Validator.isNull(value)) {
416                                            continue;
417                                    }
418    
419                                    valuesMap.put(locale, _transformValue(fieldType, value));
420                            }
421                    }
422    
423                    return valuesMap;
424            }
425    
426            private Expression _parseExpression(Condition condition) {
427                    String expression = _toExpression(condition);
428    
429                    try {
430                            ExpressionParser expressionParser = new SpelExpressionParser();
431    
432                            return expressionParser.parseExpression(expression);
433                    }
434                    catch (ParseException pe) {
435                            _log.error("Unable to parse expression " + expression, pe);
436                    }
437    
438                    return null;
439            }
440    
441            private String _toExpression(Condition condition) {
442                    if (condition.isJunction()) {
443                            Junction junction = (Junction)condition;
444    
445                            return StringPool.OPEN_PARENTHESIS.concat(
446                                    _toExpression(junction)).concat(StringPool.CLOSE_PARENTHESIS);
447                    }
448                    else {
449                            FieldCondition fieldCondition = (FieldCondition)condition;
450    
451                            return _toExpression(fieldCondition);
452                    }
453            }
454    
455            private String _toExpression(FieldCondition fieldCondition) {
456                    StringBundler sb = new StringBundler(5);
457    
458                    sb.append("(@");
459                    sb.append(fieldCondition.getName());
460    
461                    ComparisonOperator comparisonOperator =
462                            fieldCondition.getComparisonOperator();
463    
464                    if (comparisonOperator.equals(ComparisonOperator.LIKE)) {
465                            sb.append(".data matches ");
466                    }
467                    else {
468                            sb.append(".data == ");
469                    }
470    
471                    String value = StringUtil.quote(
472                            String.valueOf(fieldCondition.getValue()));
473    
474                    sb.append(value);
475                    sb.append(StringPool.CLOSE_PARENTHESIS);
476    
477                    return sb.toString();
478            }
479    
480            private String _toExpression(Junction junction) {
481                    StringBundler sb = new StringBundler();
482    
483                    LogicalOperator logicalOperator = junction.getLogicalOperator();
484    
485                    Iterator<Condition> itr = junction.iterator();
486    
487                    while (itr.hasNext()) {
488                            Condition condition = itr.next();
489    
490                            sb.append(_toExpression(condition));
491    
492                            if (itr.hasNext()) {
493                                    sb.append(StringPool.SPACE);
494                                    sb.append(logicalOperator.toString());
495                                    sb.append(StringPool.SPACE);
496                            }
497                    }
498    
499                    return sb.toString();
500            }
501    
502            private String[] _toStringArray(String type, Serializable[] values) {
503                    String[] stringValues = new String[values.length];
504    
505                    for (int i = 0; i < values.length; i++) {
506                            Serializable serializable = values[i];
507    
508                            if (FieldConstants.isNumericType(type) && (serializable == null)) {
509                                    serializable = _NUMERIC_NULL_VALUE;
510                            }
511    
512                            stringValues[i] = String.valueOf(serializable);
513                    }
514    
515                    return stringValues;
516            }
517    
518            private List<Serializable> _transformValue(String type, String value) {
519                    List<Serializable> serializables = new ArrayList<Serializable>();
520    
521                    if (FieldConstants.isNumericType(type) &&
522                            _NUMERIC_NULL_VALUE.equals(value)) {
523    
524                            value = null;
525                    }
526    
527                    Serializable serializable = FieldConstants.getSerializable(type, value);
528    
529                    serializables.add(serializable);
530    
531                    return serializables;
532            }
533    
534            private List<Serializable> _transformValue(String type, String[] values) {
535                    List<Serializable> serializables = new ArrayList<Serializable>();
536    
537                    for (String value : values) {
538                            if (FieldConstants.isNumericType(type) &&
539                                    _NUMERIC_NULL_VALUE.equals(value)) {
540    
541                                    value = null;
542                            }
543    
544                            Serializable serializable = FieldConstants.getSerializable(
545                                    type, value);
546    
547                            serializables.add(serializable);
548                    }
549    
550                    return serializables;
551            }
552    
553            private void _updateFields(
554                            ExpandoTable expandoTable, long classPK, Fields fields)
555                    throws PortalException, SystemException {
556    
557                    Iterator<Field> itr = fields.iterator(true);
558    
559                    while (itr.hasNext()) {
560                            Field field = itr.next();
561    
562                            Map<Locale, ?> dataMap = null;
563    
564                            if (field.isRepeatable()) {
565                                    Map<Locale, String[]> stringArrayMap =
566                                            new HashMap<Locale, String[]>();
567    
568                                    for (Locale locale : field.getAvailableLocales()) {
569                                            Serializable value = field.getValue(locale);
570    
571                                            if (value instanceof Date[]) {
572                                                    Date[] dates = (Date[])value;
573    
574                                                    String[] values = new String[dates.length];
575    
576                                                    for (int i = 0; i < dates.length; i++) {
577                                                            values[i] = String.valueOf(dates[i].getTime());
578                                                    }
579    
580                                                    stringArrayMap.put(locale, values);
581                                            }
582                                            else {
583                                                    String[] values = _toStringArray(
584                                                            field.getDataType(), (Serializable[])value);
585    
586                                                    stringArrayMap.put(locale, values);
587                                            }
588                                    }
589    
590                                    dataMap = stringArrayMap;
591                            }
592                            else {
593                                    Map<Locale, String> stringMap = new HashMap<Locale, String>();
594    
595                                    for (Locale locale : field.getAvailableLocales()) {
596                                            Serializable value = field.getValue(locale);
597    
598                                            if (FieldConstants.isNumericType(field.getDataType()) &&
599                                                    (value == null)) {
600    
601                                                    value = _NUMERIC_NULL_VALUE;
602                                            }
603                                            else if (value instanceof Date) {
604                                                    Date date = (Date)value;
605    
606                                                    value = date.getTime();
607                                            }
608    
609                                            stringMap.put(locale, String.valueOf(value));
610                                    }
611    
612                                    dataMap = stringMap;
613                            }
614    
615                            ExpandoValueLocalServiceUtil.addValue(
616                                    expandoTable.getCompanyId(),
617                                    ExpandoStorageAdapter.class.getName(), expandoTable.getName(),
618                                    field.getName(), classPK, dataMap, field.getDefaultLocale());
619                    }
620            }
621    
622            private static final String _NUMERIC_NULL_VALUE = "NUMERIC_NULL_VALUE";
623    
624            private static Log _log = LogFactoryUtil.getLog(
625                    ExpandoStorageAdapter.class);
626    
627    }