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