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.model.impl;
016    
017    import com.liferay.portal.LocaleException;
018    import com.liferay.portal.kernel.configuration.Filter;
019    import com.liferay.portal.kernel.exception.PortalException;
020    import com.liferay.portal.kernel.exception.SystemException;
021    import com.liferay.portal.kernel.log.Log;
022    import com.liferay.portal.kernel.log.LogFactoryUtil;
023    import com.liferay.portal.kernel.util.ArrayUtil;
024    import com.liferay.portal.kernel.util.GetterUtil;
025    import com.liferay.portal.kernel.util.HtmlUtil;
026    import com.liferay.portal.kernel.util.LocaleUtil;
027    import com.liferay.portal.kernel.util.PropsKeys;
028    import com.liferay.portal.kernel.util.PropsUtil;
029    import com.liferay.portal.kernel.util.StringBundler;
030    import com.liferay.portal.kernel.util.StringPool;
031    import com.liferay.portal.kernel.util.StringUtil;
032    import com.liferay.portal.kernel.util.Validator;
033    import com.liferay.portal.kernel.xml.Attribute;
034    import com.liferay.portal.kernel.xml.Document;
035    import com.liferay.portal.kernel.xml.Element;
036    import com.liferay.portal.kernel.xml.Node;
037    import com.liferay.portal.kernel.xml.SAXReaderUtil;
038    import com.liferay.portal.kernel.xml.XPath;
039    import com.liferay.portal.model.CacheField;
040    import com.liferay.portal.model.Group;
041    import com.liferay.portal.theme.ThemeDisplay;
042    import com.liferay.portal.util.PortalUtil;
043    import com.liferay.portal.util.PropsValues;
044    import com.liferay.portlet.dynamicdatamapping.StructureFieldException;
045    import com.liferay.portlet.dynamicdatamapping.model.DDMStructure;
046    import com.liferay.portlet.dynamicdatamapping.model.DDMTemplate;
047    import com.liferay.portlet.dynamicdatamapping.service.DDMStructureLocalServiceUtil;
048    import com.liferay.portlet.dynamicdatamapping.service.DDMTemplateLocalServiceUtil;
049    import com.liferay.portlet.dynamicdatamapping.util.DDMXMLUtil;
050    
051    import java.util.ArrayList;
052    import java.util.HashMap;
053    import java.util.LinkedHashMap;
054    import java.util.List;
055    import java.util.Locale;
056    import java.util.Map;
057    import java.util.Set;
058    import java.util.concurrent.ConcurrentHashMap;
059    
060    /**
061     * @author Brian Wing Shun Chan
062     */
063    public class DDMStructureImpl extends DDMStructureBaseImpl {
064    
065            @Override
066            public String[] getAvailableLanguageIds() {
067                    Document document = getDocument();
068    
069                    Element rootElement = document.getRootElement();
070    
071                    String availableLocales = rootElement.attributeValue(
072                            "available-locales");
073    
074                    return StringUtil.split(availableLocales);
075            }
076    
077            @Override
078            public List<String> getChildrenFieldNames(String fieldName)
079                    throws PortalException, SystemException {
080    
081                    List<String> fieldNames = new ArrayList<String>();
082    
083                    Map<String, Map<String, String>> fieldsMap = getFieldsMap();
084    
085                    for (Map<String, String> field : fieldsMap.values()) {
086                            String parentNameKey = _getPrivateAttributeKey("parentName");
087    
088                            String parentName = field.get(parentNameKey);
089    
090                            if (fieldName.equals(parentName)) {
091                                    fieldNames.add(field.get("name"));
092                            }
093                    }
094    
095                    return fieldNames;
096            }
097    
098            @Override
099            public String getCompleteXsd() throws PortalException, SystemException {
100                    if (getParentStructureId() == 0) {
101                            return getXsd();
102                    }
103    
104                    DDMStructure parentStructure =
105                            DDMStructureLocalServiceUtil.getStructure(getParentStructureId());
106    
107                    return _mergeXsds(getXsd(), parentStructure.getCompleteXsd());
108            }
109    
110            @Override
111            public String getDefaultLanguageId() {
112                    Document document = getDocument();
113    
114                    if (document == null) {
115                            Locale locale = LocaleUtil.getSiteDefault();
116    
117                            return locale.toString();
118                    }
119    
120                    Element rootElement = document.getRootElement();
121    
122                    return rootElement.attributeValue("default-locale");
123            }
124    
125            @Override
126            public Document getDocument() {
127                    if (_document == null) {
128                            try {
129                                    _document = SAXReaderUtil.read(getXsd());
130                            }
131                            catch (Exception e) {
132                                    StackTraceElement[] stackTraceElements = e.getStackTrace();
133    
134                                    for (StackTraceElement stackTraceElement : stackTraceElements) {
135                                            String className = stackTraceElement.getClassName();
136    
137                                            if (className.endsWith("DDMStructurePersistenceTest")) {
138                                                    return null;
139                                            }
140                                    }
141    
142                                    _log.error(e, e);
143                            }
144                    }
145    
146                    return _document;
147            }
148    
149            @Override
150            public String getFieldDataType(String fieldName)
151                    throws PortalException, SystemException {
152    
153                    return getFieldProperty(fieldName, "dataType");
154            }
155    
156            @Override
157            public String getFieldLabel(String fieldName, Locale locale)
158                    throws PortalException, SystemException {
159    
160                    return getFieldLabel(fieldName, LocaleUtil.toLanguageId(locale));
161            }
162    
163            @Override
164            public String getFieldLabel(String fieldName, String locale)
165                    throws PortalException, SystemException {
166    
167                    return GetterUtil.getString(
168                            getFieldProperty(fieldName, "label", locale), fieldName);
169            }
170    
171            @Override
172            public Set<String> getFieldNames() throws PortalException, SystemException {
173                    Map<String, Map<String, String>> fieldsMap = getFieldsMap();
174    
175                    return fieldsMap.keySet();
176            }
177    
178            @Override
179            public String getFieldProperty(String fieldName, String property)
180                    throws PortalException, SystemException {
181    
182                    return getFieldProperty(fieldName, property, getDefaultLanguageId());
183            }
184    
185            @Override
186            public String getFieldProperty(
187                            String fieldName, String property, String locale)
188                    throws PortalException, SystemException {
189    
190                    if (!hasField(fieldName)) {
191                            throw new StructureFieldException();
192                    }
193    
194                    Map<String, Map<String, String>> fieldsMap = getFieldsMap(locale);
195    
196                    Map<String, String> field = fieldsMap.get(fieldName);
197    
198                    return field.get(property);
199            }
200    
201            @Override
202            public boolean getFieldRepeatable(String fieldName)
203                    throws PortalException, SystemException {
204    
205                    return GetterUtil.getBoolean(getFieldProperty(fieldName, "repeatable"));
206            }
207    
208            @Override
209            public boolean getFieldRequired(String fieldName)
210                    throws PortalException, SystemException {
211    
212                    return GetterUtil.getBoolean(getFieldProperty(fieldName, "required"));
213            }
214    
215            @Override
216            public Map<String, String> getFields(
217                    String fieldName, String attributeName, String attributeValue) {
218    
219                    return getFields(
220                            fieldName, attributeName, attributeValue, getDefaultLanguageId());
221            }
222    
223            @Override
224            public Map<String, String> getFields(
225                    String fieldName, String attributeName, String attributeValue,
226                    String locale) {
227    
228                    try {
229                            if ((attributeName == null) || (attributeValue == null)) {
230                                    return null;
231                            }
232    
233                            Map<String, Map<String, String>> fieldsMap = getTransientFieldsMap(
234                                    locale);
235    
236                            for (Map<String, String> fields : fieldsMap.values()) {
237                                    String parentName = fields.get(
238                                            _getPrivateAttributeKey("parentName"));
239    
240                                    if (!fieldName.equals(parentName)) {
241                                            continue;
242                                    }
243    
244                                    if (attributeValue.equals(fields.get(attributeName))) {
245                                            return fields;
246                                    }
247                            }
248                    }
249                    catch (Exception e) {
250                            _log.error(e, e);
251                    }
252    
253                    return null;
254            }
255    
256            @Override
257            public Map<String, Map<String, String>> getFieldsMap()
258                    throws PortalException, SystemException {
259    
260                    return getFieldsMap(getDefaultLanguageId());
261            }
262    
263            @Override
264            public Map<String, Map<String, String>> getFieldsMap(String locale)
265                    throws PortalException, SystemException {
266    
267                    _indexFieldsMap(locale);
268    
269                    Map<String, Map<String, String>> fieldsMap = _localizedFieldsMap.get(
270                            locale);
271    
272                    return fieldsMap;
273            }
274    
275            @Override
276            public String getFieldTip(String fieldName, Locale locale)
277                    throws PortalException, SystemException {
278    
279                    return getFieldTip(fieldName, LocaleUtil.toLanguageId(locale));
280            }
281    
282            @Override
283            public String getFieldTip(String fieldName, String locale)
284                    throws PortalException, SystemException {
285    
286                    return GetterUtil.getString(
287                            getFieldProperty(fieldName, "tip", locale), fieldName);
288            }
289    
290            @Override
291            public String getFieldType(String fieldName)
292                    throws PortalException, SystemException {
293    
294                    return getFieldProperty(fieldName, "type");
295            }
296    
297            @Override
298            public Map<String, Map<String, Map<String, String>>>
299                    getLocalizedFieldsMap() {
300    
301                    return _localizedFieldsMap;
302            }
303    
304            @Override
305            public Map<String, Map<String, Map<String, String>>>
306                    getLocalizedTransientFieldsMap() {
307    
308                    return _localizedTransientFieldsMap;
309            }
310    
311            @Override
312            public List<String> getRootFieldNames()
313                    throws PortalException, SystemException {
314    
315                    List<String> fieldNames = new ArrayList<String>();
316    
317                    Map<String, Map<String, String>> fieldsMap = getFieldsMap();
318    
319                    for (Map.Entry<String, Map<String, String>> entry :
320                                    fieldsMap.entrySet()) {
321    
322                            Map<String, String> field = entry.getValue();
323    
324                            String parentNameKey = _getPrivateAttributeKey("parentName");
325    
326                            if (!field.containsKey(parentNameKey)) {
327                                    fieldNames.add(entry.getKey());
328                            }
329                    }
330    
331                    return fieldNames;
332            }
333    
334            @Override
335            public List<DDMTemplate> getTemplates() throws SystemException {
336                    return DDMTemplateLocalServiceUtil.getTemplates(getStructureId());
337            }
338    
339            @Override
340            public Map<String, Map<String, String>> getTransientFieldsMap(String locale)
341                    throws PortalException, SystemException {
342    
343                    _indexFieldsMap(locale);
344    
345                    Map<String, Map<String, String>> fieldsMap =
346                            _localizedTransientFieldsMap.get(locale);
347    
348                    return fieldsMap;
349            }
350    
351            /**
352             * Returns the WebDAV URL to access the structure.
353             *
354             * @param  themeDisplay the theme display needed to build the URL. It can
355             *         set HTTPS access, the server name, the server port, the path
356             *         context, and the scope group.
357             * @param  webDAVToken the WebDAV token for the URL
358             * @return the WebDAV URL
359             */
360            @Override
361            public String getWebDavURL(ThemeDisplay themeDisplay, String webDAVToken) {
362                    StringBundler sb = new StringBundler(11);
363    
364                    boolean secure = false;
365    
366                    if (themeDisplay.isSecure() ||
367                            PropsValues.WEBDAV_SERVLET_HTTPS_REQUIRED) {
368    
369                            secure = true;
370                    }
371    
372                    String portalURL = PortalUtil.getPortalURL(
373                            themeDisplay.getServerName(), themeDisplay.getServerPort(), secure);
374    
375                    sb.append(portalURL);
376    
377                    sb.append(themeDisplay.getPathContext());
378                    sb.append(StringPool.SLASH);
379                    sb.append("webdav");
380    
381                    Group group = themeDisplay.getScopeGroup();
382    
383                    sb.append(group.getFriendlyURL());
384    
385                    sb.append(StringPool.SLASH);
386                    sb.append(webDAVToken);
387                    sb.append(StringPool.SLASH);
388                    sb.append("Structures");
389                    sb.append(StringPool.SLASH);
390                    sb.append(getStructureId());
391    
392                    return sb.toString();
393            }
394    
395            @Override
396            public boolean hasField(String fieldName)
397                    throws PortalException, SystemException {
398    
399                    Map<String, Map<String, String>> fieldsMap = getFieldsMap();
400    
401                    boolean hasField = fieldsMap.containsKey(fieldName);
402    
403                    while (!hasField && (getParentStructureId() > 0)) {
404                            DDMStructure parentStructure =
405                                    DDMStructureLocalServiceUtil.getStructure(
406                                            getParentStructureId());
407    
408                            hasField = parentStructure.hasField(fieldName);
409                    }
410    
411                    return hasField;
412            }
413    
414            @Override
415            public boolean isFieldPrivate(String fieldName)
416                    throws PortalException, SystemException {
417    
418                    return GetterUtil.getBoolean(getFieldProperty(fieldName, "private"));
419            }
420    
421            @Override
422            public boolean isFieldRepeatable(String fieldName)
423                    throws PortalException, SystemException {
424    
425                    return GetterUtil.getBoolean(getFieldProperty(fieldName, "repeatable"));
426            }
427    
428            @Override
429            public void prepareLocalizedFieldsForImport(Locale defaultImportLocale)
430                    throws LocaleException {
431    
432                    super.prepareLocalizedFieldsForImport(defaultImportLocale);
433    
434                    Locale ddmStructureDefaultLocale = LocaleUtil.fromLanguageId(
435                            getDefaultLanguageId());
436    
437                    try {
438                            setXsd(
439                                    DDMXMLUtil.updateXMLDefaultLocale(
440                                            getXsd(), ddmStructureDefaultLocale, defaultImportLocale));
441                    }
442                    catch (Exception e) {
443                            throw new LocaleException(LocaleException.TYPE_EXPORT_IMPORT, e);
444                    }
445            }
446    
447            @Override
448            public void setDocument(Document document) {
449                    _document = document;
450            }
451    
452            @Override
453            public void setLocalizedFieldsMap(
454                    Map<String, Map<String, Map<String, String>>> localizedFieldsMap) {
455    
456                    _localizedFieldsMap = localizedFieldsMap;
457            }
458    
459            @Override
460            public void setLocalizedTransientFieldsMap(
461                    Map<String, Map<String, Map<String, String>>>
462                            localizedTransientFieldsMap) {
463    
464                    _localizedTransientFieldsMap = localizedTransientFieldsMap;
465            }
466    
467            @Override
468            public void setXsd(String xsd) {
469                    super.setXsd(xsd);
470    
471                    _document = null;
472                    _localizedFieldsMap.clear();
473                    _localizedTransientFieldsMap.clear();
474            }
475    
476            private Map<String, String> _getField(Element element, String locale) {
477                    Map<String, String> field = new HashMap<String, String>();
478    
479                    String[] availableLanguageIds = getAvailableLanguageIds();
480    
481                    if ((locale != null) &&
482                            !ArrayUtil.contains(availableLanguageIds, locale)) {
483    
484                            locale = getDefaultLanguageId();
485                    }
486    
487                    locale = HtmlUtil.escapeXPathAttribute(locale);
488    
489                    String xPathExpression =
490                            "meta-data[@locale=".concat(locale).concat("]");
491    
492                    XPath xPathSelector = SAXReaderUtil.createXPath(xPathExpression);
493    
494                    Node node = xPathSelector.selectSingleNode(element);
495    
496                    Element metaDataElement = (Element)node.asXPathResult(node.getParent());
497    
498                    if (metaDataElement != null) {
499                            List<Element> childMetaDataElements = metaDataElement.elements();
500    
501                            for (Element childMetaDataElement : childMetaDataElements) {
502                                    String name = childMetaDataElement.attributeValue("name");
503                                    String value = childMetaDataElement.getText();
504    
505                                    field.put(name, value);
506                            }
507                    }
508    
509                    for (Attribute attribute : element.attributes()) {
510                            field.put(attribute.getName(), attribute.getValue());
511                    }
512    
513                    Element parentElement = element.getParent();
514    
515                    if (parentElement != null) {
516                            String parentName = parentElement.attributeValue("name");
517    
518                            if (Validator.isNotNull(parentName)) {
519                                    field.put(_getPrivateAttributeKey("parentName"), parentName);
520                            }
521                    }
522    
523                    return field;
524            }
525    
526            private String _getPrivateAttributeKey(String attributeName) {
527                    return StringPool.UNDERLINE.concat(attributeName).concat(
528                            StringPool.UNDERLINE);
529            }
530    
531            private Map<String, String> _getPrivateField(String privateFieldName) {
532                    Map<String, String> privateField = new HashMap<String, String>();
533    
534                    String dataType = PropsUtil.get(
535                            PropsKeys.DYNAMIC_DATA_MAPPING_STRUCTURE_PRIVATE_FIELD_DATATYPE,
536                            new Filter(privateFieldName));
537    
538                    privateField.put("dataType", dataType);
539    
540                    privateField.put("name", privateFieldName);
541                    privateField.put("private", Boolean.TRUE.toString());
542    
543                    String repeatable = PropsUtil.get(
544                            PropsKeys.DYNAMIC_DATA_MAPPING_STRUCTURE_PRIVATE_FIELD_REPEATABLE,
545                            new Filter(privateFieldName));
546    
547                    privateField.put("repeatable", repeatable);
548    
549                    return privateField;
550            }
551    
552            private void _indexFieldsMap(String locale)
553                    throws PortalException, SystemException {
554    
555                    Map<String, Map<String, String>> fieldsMap = _localizedFieldsMap.get(
556                            locale);
557                    Map<String, Map<String, String>> transientFieldsMap =
558                            _localizedTransientFieldsMap.get(locale);
559    
560                    if (fieldsMap != null) {
561                            return;
562                    }
563    
564                    if (getParentStructureId() > 0) {
565                            DDMStructure parentStructure =
566                                    DDMStructureLocalServiceUtil.getStructure(
567                                            getParentStructureId());
568    
569                            fieldsMap = parentStructure.getFieldsMap(locale);
570                            transientFieldsMap = parentStructure.getTransientFieldsMap(locale);
571                    }
572                    else {
573                            fieldsMap = new LinkedHashMap<String, Map<String, String>>();
574                            transientFieldsMap =
575                                    new LinkedHashMap<String, Map<String, String>>();
576                    }
577    
578                    XPath xPathSelector = SAXReaderUtil.createXPath("//dynamic-element");
579    
580                    List<Node> nodes = xPathSelector.selectNodes(getDocument());
581    
582                    for (Node node : nodes) {
583                            Element element = (Element)node;
584    
585                            String name = element.attributeValue("name");
586    
587                            if (Validator.isNotNull(element.attributeValue("dataType"))) {
588                                    fieldsMap.put(name, _getField(element, locale));
589                            }
590                            else {
591                                    transientFieldsMap.put(name, _getField(element, locale));
592                            }
593                    }
594    
595                    String[] privateFieldNames =
596                            PropsValues.DYNAMIC_DATA_MAPPING_STRUCTURE_PRIVATE_FIELD_NAMES;
597    
598                    for (String privateFieldName : privateFieldNames) {
599                            Map<String, String> privateField = _getPrivateField(
600                                    privateFieldName);
601    
602                            fieldsMap.put(privateFieldName, privateField);
603                    }
604    
605                    _localizedFieldsMap.put(locale, fieldsMap);
606                    _localizedTransientFieldsMap.put(locale, transientFieldsMap);
607            }
608    
609            private String _mergeXsds(String xsd1, String xsd2) throws SystemException {
610                    try {
611                            Document document1 = SAXReaderUtil.read(xsd1);
612                            Document document2 = SAXReaderUtil.read(xsd2);
613    
614                            Element rootElement1 = document1.getRootElement();
615                            Element rootElement2 = document2.getRootElement();
616    
617                            for (Element element : rootElement1.elements()) {
618                                    rootElement1.remove(element);
619    
620                                    rootElement2.add(element);
621                            }
622    
623                            return rootElement2.formattedString();
624                    }
625                    catch (Exception e) {
626                            throw new SystemException(e);
627                    }
628            }
629    
630            private static Log _log = LogFactoryUtil.getLog(DDMStructureImpl.class);
631    
632            @CacheField
633            private Document _document;
634    
635            @CacheField
636            private Map<String, Map<String, Map<String, String>>> _localizedFieldsMap =
637                    new ConcurrentHashMap<String, Map<String, Map<String, String>>>();
638    
639            @CacheField
640            private Map<String, Map<String, Map<String, String>>>
641                    _localizedTransientFieldsMap =
642                            new ConcurrentHashMap<String, Map<String, Map<String, String>>>();
643    
644    }