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