001    /**
002     * Copyright (c) 2000-2012 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.HashMap;
056    import java.util.Iterator;
057    import java.util.List;
058    import java.util.Locale;
059    import java.util.Map;
060    
061    import org.springframework.expression.EvaluationException;
062    import org.springframework.expression.Expression;
063    import org.springframework.expression.ExpressionParser;
064    import org.springframework.expression.ParseException;
065    import org.springframework.expression.spel.standard.SpelExpressionParser;
066    import org.springframework.expression.spel.support.StandardEvaluationContext;
067    
068    /**
069     * @author Eduardo Lundgren
070     * @author Brian Wing Shun Chan
071     * @author Marcellus Tavares
072     */
073    public class ExpandoStorageAdapter extends BaseStorageAdapter {
074    
075            @Override
076            protected long doCreate(
077                            long companyId, long ddmStructureId, Fields fields,
078                            ServiceContext serviceContext)
079                    throws Exception {
080    
081                    ExpandoTable expandoTable = _getExpandoTable(
082                            companyId, ddmStructureId, fields);
083    
084                    ExpandoRow expandoRow = ExpandoRowLocalServiceUtil.addRow(
085                            expandoTable.getTableId(), CounterLocalServiceUtil.increment());
086    
087                    _updateFields(expandoTable, expandoRow.getClassPK(), fields);
088    
089                    DDMStorageLinkLocalServiceUtil.addStorageLink(
090                            expandoTable.getClassNameId(), expandoRow.getRowId(),
091                            ddmStructureId, serviceContext);
092    
093                    return expandoRow.getRowId();
094            }
095    
096            @Override
097            protected void doDeleteByClass(long classPK) throws Exception {
098                    ExpandoRowLocalServiceUtil.deleteRow(classPK);
099    
100                    DDMStorageLinkLocalServiceUtil.deleteClassStorageLink(classPK);
101            }
102    
103            @Override
104            protected void doDeleteByDDMStructure(long ddmStructureId)
105                    throws Exception {
106    
107                    List<DDMStorageLink> ddmStorageLinks =
108                            DDMStorageLinkLocalServiceUtil.getStructureStorageLinks(
109                                    ddmStructureId);
110    
111                    for (DDMStorageLink ddmStorageLink : ddmStorageLinks) {
112                            ExpandoRowLocalServiceUtil.deleteRow(ddmStorageLink.getClassPK());
113                    }
114    
115                    DDMStorageLinkLocalServiceUtil.deleteStructureStorageLinks(
116                            ddmStructureId);
117            }
118    
119            @Override
120            protected List<Fields> doGetFieldsListByClasses(
121                            long ddmStructureId, long[] classPKs, List<String> fieldNames,
122                            OrderByComparator orderByComparator)
123                    throws Exception {
124    
125                    return _doQuery(
126                            ddmStructureId, classPKs, fieldNames, null, orderByComparator);
127            }
128    
129            @Override
130            protected List<Fields> doGetFieldsListByDDMStructure(
131                            long ddmStructureId, List<String> fieldNames,
132                            OrderByComparator orderByComparator)
133                    throws Exception {
134    
135                    return _doQuery(ddmStructureId, fieldNames, null, orderByComparator);
136            }
137    
138            @Override
139            protected Map<Long, Fields> doGetFieldsMapByClasses(
140                            long ddmStructureId, long[] classPKs, List<String> fieldNames)
141                    throws Exception {
142    
143                    return _doQuery(ddmStructureId, classPKs, fieldNames);
144            }
145    
146            @Override
147            protected List<Fields> doQuery(
148                            long ddmStructureId, List<String> fieldNames, Condition condition,
149                            OrderByComparator orderByComparator)
150                    throws Exception {
151    
152                    return _doQuery(
153                            ddmStructureId, fieldNames, condition, orderByComparator);
154            }
155    
156            @Override
157            protected int doQueryCount(long ddmStructureId, Condition condition)
158                    throws Exception {
159    
160                    Expression expression = null;
161    
162                    if (condition != null) {
163                            expression = _parseExpression(condition);
164                    }
165    
166                    int count = 0;
167    
168                    long[] expandoRowIds = _getExpandoRowIds(ddmStructureId);
169    
170                    for (long expandoRowId : expandoRowIds) {
171                            List<ExpandoValue> expandoValues =
172                                    ExpandoValueLocalServiceUtil.getRowValues(expandoRowId);
173    
174                            if ((expression == null) ||
175                                    ((expression != null) &&
176                                     _booleanValueOf(expression, expandoValues))) {
177    
178                                    count++;
179                            }
180                    }
181    
182                    return count;
183            }
184    
185            @Override
186            protected void doUpdate(
187                            long classPK, Fields fields, boolean mergeFields,
188                            ServiceContext serviceContext)
189                    throws Exception {
190    
191                    ExpandoRow expandoRow = ExpandoRowLocalServiceUtil.getRow(classPK);
192    
193                    DDMStorageLink ddmStorageLink =
194                            DDMStorageLinkLocalServiceUtil.getClassStorageLink(
195                                    expandoRow.getRowId());
196    
197                    ExpandoTable expandoTable = _getExpandoTable(
198                            expandoRow.getCompanyId(), ddmStorageLink.getStructureId(), fields);
199    
200                    if (mergeFields) {
201                            fields = DDMUtil.mergeFields(fields, getFields(classPK));
202                    }
203    
204                    ExpandoValueLocalServiceUtil.deleteRowValues(expandoRow.getRowId());
205    
206                    _updateFields(expandoTable, expandoRow.getClassPK(), fields);
207            }
208    
209            private boolean _booleanValueOf(
210                    Expression expression, List<ExpandoValue> expandoValues) {
211    
212                    try {
213                            StandardEvaluationContext standardEvaluationContext =
214                                    new StandardEvaluationContext();
215    
216                            standardEvaluationContext.setBeanResolver(
217                                    new ExpandoValueBeanResolver(expandoValues));
218    
219                            return expression.getValue(
220                                    standardEvaluationContext, Boolean.class);
221                    }
222                    catch (EvaluationException ee) {
223                            _log.error("Unable to evaluate expression", ee);
224                    }
225    
226                    return false;
227            }
228    
229            private void _checkExpandoColumns(
230                            long ddmStructureId, 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.getFieldType(fieldName);
326    
327                                                    Map<Locale, List<Serializable>> valuesMap =
328                                                            _getValuesMap(
329                                                                    column.getType(), fieldType,
330                                                                    expandoValue.getSerializable());
331    
332                                                    field.setValuesMap(valuesMap);
333    
334                                                    fields.put(field);
335                                            }
336                                    }
337    
338                                    fieldsList.add(fields);
339                            }
340                    }
341    
342                    if (orderByComparator != null) {
343                            Collections.sort(fieldsList, orderByComparator);
344                    }
345    
346                    return fieldsList;
347            }
348    
349            private long[] _getExpandoRowIds(long ddmStructureId)
350                    throws SystemException {
351    
352                    List<Long> expandoRowIds = new ArrayList<Long>();
353    
354                    List<DDMStorageLink> ddmStorageLinks =
355                            DDMStorageLinkLocalServiceUtil.getStructureStorageLinks(
356                                    ddmStructureId);
357    
358                    for (DDMStorageLink ddmStorageLink : ddmStorageLinks) {
359                            expandoRowIds.add(ddmStorageLink.getClassPK());
360                    }
361    
362                    return ArrayUtil.toArray(
363                            expandoRowIds.toArray(new Long[expandoRowIds.size()]));
364            }
365    
366            private ExpandoTable _getExpandoTable(
367                            long companyId, long ddmStructureId, Fields fields)
368                    throws PortalException, SystemException {
369    
370                    ExpandoTable expandoTable = null;
371    
372                    long classNameId = PortalUtil.getClassNameId(
373                            ExpandoStorageAdapter.class.getName());
374    
375                    try {
376                            expandoTable = ExpandoTableLocalServiceUtil.getTable(
377                                    companyId, classNameId, String.valueOf(ddmStructureId));
378                    }
379                    catch (NoSuchTableException nste) {
380                            expandoTable = ExpandoTableLocalServiceUtil.addTable(
381                                    companyId, classNameId, String.valueOf(ddmStructureId));
382                    }
383    
384                    _checkExpandoColumns(ddmStructureId, expandoTable, fields);
385    
386                    return expandoTable;
387            }
388    
389            private Map<Locale, List<Serializable>> _getValuesMap(
390                    int columnType, String fieldType, Serializable data) {
391    
392                    Map<Locale, List<Serializable>> valuesMap =
393                            new HashMap<Locale, List<Serializable>>();
394    
395                    if (columnType == ExpandoColumnConstants.STRING_ARRAY_LOCALIZED) {
396                            Map<Locale, String[]> stringArrayMap = (Map<Locale, String[]>)data;
397    
398                            for (Locale locale : stringArrayMap.keySet()) {
399                                    String[] value = stringArrayMap.get(locale);
400    
401                                    if (Validator.isNull(value)) {
402                                            continue;
403                                    }
404    
405                                    valuesMap.put(locale, _transformValue(fieldType, value));
406                            }
407                    }
408                    else {
409                            Map<Locale, String> stringMap = (Map<Locale, String>)data;
410    
411                            for (Locale locale : stringMap.keySet()) {
412                                    String value = stringMap.get(locale);
413    
414                                    if (Validator.isNull(value)) {
415                                            continue;
416                                    }
417    
418                                    valuesMap.put(locale, _transformValue(fieldType, value));
419                            }
420                    }
421    
422                    return valuesMap;
423            }
424    
425            private Expression _parseExpression(Condition condition) {
426                    String expression = _toExpression(condition);
427    
428                    try {
429                            ExpressionParser expressionParser = new SpelExpressionParser();
430    
431                            return expressionParser.parseExpression(expression);
432                    }
433                    catch (ParseException pe) {
434                            _log.error("Unable to parse expression " + expression, pe);
435                    }
436    
437                    return null;
438            }
439    
440            private String _toExpression(Condition condition) {
441                    if (condition.isJunction()) {
442                            Junction junction = (Junction)condition;
443    
444                            return StringPool.OPEN_PARENTHESIS.concat(
445                                    _toExpression(junction)).concat(StringPool.CLOSE_PARENTHESIS);
446                    }
447                    else {
448                            FieldCondition fieldCondition = (FieldCondition)condition;
449    
450                            return _toExpression(fieldCondition);
451                    }
452            }
453    
454            private String _toExpression(FieldCondition fieldCondition) {
455                    StringBundler sb = new StringBundler(5);
456    
457                    sb.append("(@");
458                    sb.append(fieldCondition.getName());
459    
460                    ComparisonOperator comparisonOperator =
461                            fieldCondition.getComparisonOperator();
462    
463                    if (comparisonOperator.equals(ComparisonOperator.LIKE)) {
464                            sb.append(".data matches ");
465                    }
466                    else {
467                            sb.append(".data == ");
468                    }
469    
470                    String value = StringUtil.quote(
471                            String.valueOf(fieldCondition.getValue()));
472    
473                    sb.append(value);
474                    sb.append(StringPool.CLOSE_PARENTHESIS);
475    
476                    return sb.toString();
477            }
478    
479            private String _toExpression(Junction junction) {
480                    StringBundler sb = new StringBundler();
481    
482                    LogicalOperator logicalOperator = junction.getLogicalOperator();
483    
484                    Iterator<Condition> itr = junction.iterator();
485    
486                    while (itr.hasNext()) {
487                            Condition condition = itr.next();
488    
489                            sb.append(_toExpression(condition));
490    
491                            if (itr.hasNext()) {
492                                    sb.append(StringPool.SPACE);
493                                    sb.append(logicalOperator.toString());
494                                    sb.append(StringPool.SPACE);
495                            }
496                    }
497    
498                    return sb.toString();
499            }
500    
501            private List<Serializable> _transformValue(String type, String value) {
502                    List<Serializable> serializables = new ArrayList<Serializable>();
503    
504                    Serializable serializable = FieldConstants.getSerializable(type, value);
505    
506                    serializables.add(serializable);
507    
508                    return serializables;
509            }
510    
511            private List<Serializable> _transformValue(String type, String[] values) {
512                    List<Serializable> serializables = new ArrayList<Serializable>();
513    
514                    for (String value : values) {
515                            Serializable serializable = FieldConstants.getSerializable(
516                                    type, value);
517    
518                            serializables.add(serializable);
519                    }
520    
521                    return serializables;
522            }
523    
524            private void _updateFields(
525                            ExpandoTable expandoTable, long classPK, Fields fields)
526                    throws PortalException, SystemException {
527    
528                    Iterator<Field> itr = fields.iterator();
529    
530                    while (itr.hasNext()) {
531                            Field field = itr.next();
532    
533                            Map<Locale, ?> dataMap = null;
534    
535                            if (field.isRepeatable()) {
536                                    Map<Locale, String[]> stringArrayMap =
537                                            new HashMap<Locale, String[]>();
538    
539                                    for (Locale locale : field.getAvailableLocales()) {
540                                            String[] values = ArrayUtil.toStringArray(
541                                                    (Object[])field.getValue(locale));
542    
543                                            stringArrayMap.put(locale, values);
544                                    }
545    
546                                    dataMap = stringArrayMap;
547                            }
548                            else {
549                                    Map<Locale, String> stringMap = new HashMap<Locale, String>();
550    
551                                    for (Locale locale : field.getAvailableLocales()) {
552                                            String value = String.valueOf(field.getValue(locale));
553    
554                                            stringMap.put(locale, value);
555                                    }
556    
557                                    dataMap = stringMap;
558                            }
559    
560                            ExpandoValueLocalServiceUtil.addValue(
561                                    expandoTable.getCompanyId(),
562                                    ExpandoStorageAdapter.class.getName(), expandoTable.getName(),
563                                    field.getName(), classPK, dataMap, field.getDefaultLocale());
564                    }
565            }
566    
567            private static Log _log = LogFactoryUtil.getLog(
568                    ExpandoStorageAdapter.class);
569    
570    }