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.portal.model;
016    
017    import com.liferay.portal.kernel.log.Log;
018    import com.liferay.portal.kernel.log.LogFactoryUtil;
019    import com.liferay.portal.kernel.model.ModelHints;
020    import com.liferay.portal.kernel.model.ModelHintsCallback;
021    import com.liferay.portal.kernel.model.ModelHintsConstants;
022    import com.liferay.portal.kernel.util.GetterUtil;
023    import com.liferay.portal.kernel.util.ListUtil;
024    import com.liferay.portal.kernel.util.StringPool;
025    import com.liferay.portal.kernel.util.StringUtil;
026    import com.liferay.portal.kernel.util.Tuple;
027    import com.liferay.portal.kernel.util.Validator;
028    
029    import java.io.File;
030    import java.io.FileInputStream;
031    import java.io.InputStream;
032    
033    import java.net.URL;
034    
035    import java.util.ArrayList;
036    import java.util.Collections;
037    import java.util.Enumeration;
038    import java.util.HashMap;
039    import java.util.LinkedHashMap;
040    import java.util.List;
041    import java.util.Map;
042    import java.util.Set;
043    import java.util.TreeMap;
044    import java.util.TreeSet;
045    
046    import org.dom4j.Document;
047    import org.dom4j.Element;
048    import org.dom4j.io.SAXReader;
049    
050    /**
051     * @author Brian Wing Shun Chan
052     * @author Tomas Polesovsky
053     * @author Raymond Augé
054     */
055    public abstract class BaseModelHintsImpl implements ModelHints {
056    
057            public void afterPropertiesSet() {
058                    _hintCollections = new HashMap<>();
059                    _defaultHints = new HashMap<>();
060                    _modelFields = new HashMap<>();
061                    _models = new TreeSet<>();
062    
063                    try {
064                            ClassLoader classLoader = getClass().getClassLoader();
065    
066                            for (String config : getModelHintsConfigs()) {
067                                    if (config.startsWith("classpath*:")) {
068                                            String name = config.substring("classpath*:".length());
069    
070                                            Enumeration<URL> enu = classLoader.getResources(name);
071    
072                                            if (_log.isDebugEnabled() && !enu.hasMoreElements()) {
073                                                    _log.debug("No resources found for " + name);
074                                            }
075    
076                                            while (enu.hasMoreElements()) {
077                                                    URL url = enu.nextElement();
078    
079                                                    if (_log.isDebugEnabled()) {
080                                                            _log.debug("Loading " + name + " from " + url);
081                                                    }
082    
083                                                    InputStream inputStream = url.openStream();
084    
085                                                    read(classLoader, url.toString(), inputStream);
086                                            }
087                                    }
088                                    else {
089                                            InputStream inputStream = classLoader.getResourceAsStream(
090                                                    config);
091    
092                                            if (inputStream == null) {
093                                                    File file = new File(config);
094    
095                                                    if (!file.exists()) {
096                                                            continue;
097                                                    }
098    
099                                                    inputStream = new FileInputStream(file);
100                                            }
101    
102                                            try (InputStream curInputStream = inputStream) {
103                                                    read(classLoader, config, curInputStream);
104                                            }
105                                    }
106                            }
107                    }
108                    catch (Exception e) {
109                            _log.error(e, e);
110                    }
111            }
112    
113            @Override
114            public String buildCustomValidatorName(String validatorName) {
115                    return validatorName.concat(StringPool.UNDERLINE).concat(
116                            StringUtil.randomId());
117            }
118    
119            @Override
120            public Map<String, String> getDefaultHints(String model) {
121                    return _defaultHints.get(model);
122            }
123    
124            @Override
125            public Object getFieldsElement(String model, String field) {
126                    Map<String, Object> fields = (Map<String, Object>)_modelFields.get(
127                            model);
128    
129                    if (fields == null) {
130                            return null;
131                    }
132    
133                    Element fieldsEl = (Element)fields.get(field + _ELEMENTS_SUFFIX);
134    
135                    if (fieldsEl == null) {
136                            return null;
137                    }
138                    else {
139                            return fieldsEl;
140                    }
141            }
142    
143            @Override
144            public Map<String, String> getHints(String model, String field) {
145                    Map<String, Object> fields = (Map<String, Object>)_modelFields.get(
146                            model);
147    
148                    if (fields == null) {
149                            return null;
150                    }
151                    else {
152                            return (Map<String, String>)fields.get(field + _HINTS_SUFFIX);
153                    }
154            }
155    
156            @Override
157            public int getMaxLength(String model, String field) {
158                    Map<String, String> hints = getHints(model, field);
159    
160                    if (hints == null) {
161                            return Integer.MAX_VALUE;
162                    }
163    
164                    int maxLength = GetterUtil.getInteger(
165                            ModelHintsConstants.TEXT_MAX_LENGTH);
166    
167                    maxLength = GetterUtil.getInteger(hints.get("max-length"), maxLength);
168    
169                    return maxLength;
170            }
171    
172            public abstract ModelHintsCallback getModelHintsCallback();
173    
174            public abstract String[] getModelHintsConfigs();
175    
176            @Override
177            public List<String> getModels() {
178                    return ListUtil.fromCollection(_models);
179            }
180    
181            @Override
182            public Tuple getSanitizeTuple(String model, String field) {
183                    Map<String, Object> fields = (Map<String, Object>)_modelFields.get(
184                            model);
185    
186                    if (fields == null) {
187                            return null;
188                    }
189                    else {
190                            return (Tuple)fields.get(field + _SANITIZE_SUFFIX);
191                    }
192            }
193    
194            @Override
195            public List<Tuple> getSanitizeTuples(String model) {
196                    Map<String, Object> fields = (Map<String, Object>)_modelFields.get(
197                            model);
198    
199                    if (fields == null) {
200                            return Collections.emptyList();
201                    }
202    
203                    List<Tuple> sanitizeTuples = new ArrayList<>();
204    
205                    for (Map.Entry<String, Object> entry : fields.entrySet()) {
206                            String key = entry.getKey();
207    
208                            if (key.endsWith(_SANITIZE_SUFFIX)) {
209                                    Tuple sanitizeTuple = (Tuple)entry.getValue();
210    
211                                    sanitizeTuples.add(sanitizeTuple);
212                            }
213                    }
214    
215                    return sanitizeTuples;
216            }
217    
218            public abstract SAXReader getSAXReader();
219    
220            @Override
221            public String getType(String model, String field) {
222                    Map<String, Object> fields = (Map<String, Object>)_modelFields.get(
223                            model);
224    
225                    if (fields == null) {
226                            return null;
227                    }
228                    else {
229                            return (String)fields.get(field + _TYPE_SUFFIX);
230                    }
231            }
232    
233            @Override
234            public List<Tuple> getValidators(String model, String field) {
235                    Map<String, Object> fields = (Map<String, Object>)_modelFields.get(
236                            model);
237    
238                    if ((fields == null) ||
239                            (fields.get(field + _VALIDATORS_SUFFIX) == null)) {
240    
241                            return null;
242                    }
243                    else {
244                            return (List<Tuple>)fields.get(field + _VALIDATORS_SUFFIX);
245                    }
246            }
247    
248            @Override
249            public String getValue(
250                    String model, String field, String name, String defaultValue) {
251    
252                    Map<String, String> hints = getHints(model, field);
253    
254                    if (hints == null) {
255                            return defaultValue;
256                    }
257    
258                    return GetterUtil.getString(hints.get(name), defaultValue);
259            }
260    
261            @Override
262            public boolean hasField(String model, String field) {
263                    Map<String, Object> fields = (Map<String, Object>)_modelFields.get(
264                            model);
265    
266                    if (fields == null) {
267                            return false;
268                    }
269    
270                    return fields.containsKey(field + _ELEMENTS_SUFFIX);
271            }
272    
273            @Override
274            public boolean isCustomValidator(String validatorName) {
275                    if (validatorName.equals("custom")) {
276                            return true;
277                    }
278    
279                    return false;
280            }
281    
282            @Override
283            public boolean isLocalized(String model, String field) {
284                    Map<String, Object> fields = (Map<String, Object>)_modelFields.get(
285                            model);
286    
287                    if (fields == null) {
288                            return false;
289                    }
290    
291                    Boolean localized = (Boolean)fields.get(field + _LOCALIZATION_SUFFIX);
292    
293                    if (localized != null) {
294                            return localized;
295                    }
296                    else {
297                            return false;
298                    }
299            }
300    
301            @Override
302            public void read(ClassLoader classLoader, InputStream inputStream)
303                    throws Exception {
304    
305                    read(classLoader, null, inputStream);
306            }
307    
308            @Override
309            public void read(ClassLoader classLoader, String source) throws Exception {
310                    read(classLoader, source, classLoader.getResourceAsStream(source));
311            }
312    
313            public void read(
314                            ClassLoader classLoader, String source, InputStream inputStream)
315                    throws Exception {
316    
317                    if (inputStream == null) {
318                            if (_log.isWarnEnabled()) {
319                                    _log.warn("Cannot load " + source);
320                            }
321    
322                            return;
323                    }
324                    else {
325                            if (_log.isDebugEnabled()) {
326                                    _log.debug("Loading " + source);
327                            }
328                    }
329    
330                    SAXReader saxReader = getSAXReader();
331    
332                    Document document = saxReader.read(inputStream);
333    
334                    Element rootElement = document.getRootElement();
335    
336                    List<Element> rootElements = rootElement.elements("hint-collection");
337    
338                    for (Element hintCollectionElement : rootElements) {
339                            String name = hintCollectionElement.attributeValue("name");
340    
341                            Map<String, String> hints = _hintCollections.get(name);
342    
343                            if (hints == null) {
344                                    hints = new HashMap<>();
345    
346                                    _hintCollections.put(name, hints);
347                            }
348    
349                            List<Element> hintElements = hintCollectionElement.elements("hint");
350    
351                            for (Element hintElement : hintElements) {
352                                    String hintName = hintElement.attributeValue("name");
353                                    String hintValue = hintElement.getText();
354    
355                                    hints.put(hintName, hintValue);
356                            }
357                    }
358    
359                    rootElements = rootElement.elements("model");
360    
361                    for (Element modelElement : rootElements) {
362                            String name = modelElement.attributeValue("name");
363    
364                            ModelHintsCallback modelHintsCallback = getModelHintsCallback();
365    
366                            modelHintsCallback.execute(classLoader, name);
367    
368                            Map<String, String> defaultHints = new HashMap<>();
369    
370                            _defaultHints.put(name, defaultHints);
371    
372                            Element defaultHintsElement = modelElement.element("default-hints");
373    
374                            if (defaultHintsElement != null) {
375                                    List<Element> hintElements = defaultHintsElement.elements(
376                                            "hint");
377    
378                                    for (Element hintElement : hintElements) {
379                                            String hintName = hintElement.attributeValue("name");
380                                            String hintValue = hintElement.getText();
381    
382                                            defaultHints.put(hintName, hintValue);
383                                    }
384                            }
385    
386                            Map<String, Object> fields = (Map<String, Object>)_modelFields.get(
387                                    name);
388    
389                            if (fields == null) {
390                                    fields = new LinkedHashMap<>();
391    
392                                    _modelFields.put(name, fields);
393                            }
394    
395                            _models.add(name);
396    
397                            List<Element> modelElements = modelElement.elements("field");
398    
399                            for (Element fieldElement : modelElements) {
400                                    String fieldName = fieldElement.attributeValue("name");
401                                    String fieldType = fieldElement.attributeValue("type");
402                                    boolean fieldLocalized = GetterUtil.getBoolean(
403                                            fieldElement.attributeValue("localized"));
404    
405                                    Map<String, String> fieldHints = new HashMap<>();
406    
407                                    fieldHints.putAll(defaultHints);
408    
409                                    List<Element> fieldElements = fieldElement.elements(
410                                            "hint-collection");
411    
412                                    for (Element hintCollectionElement : fieldElements) {
413                                            Map<String, String> hints = _hintCollections.get(
414                                                    hintCollectionElement.attributeValue("name"));
415    
416                                            fieldHints.putAll(hints);
417                                    }
418    
419                                    fieldElements = fieldElement.elements("hint");
420    
421                                    for (Element hintElement : fieldElements) {
422                                            String hintName = hintElement.attributeValue("name");
423                                            String hintValue = hintElement.getText();
424    
425                                            fieldHints.put(hintName, hintValue);
426                                    }
427    
428                                    Tuple fieldSanitize = null;
429    
430                                    Element sanitizeElement = fieldElement.element("sanitize");
431    
432                                    if (sanitizeElement != null) {
433                                            String contentType = sanitizeElement.attributeValue(
434                                                    "content-type");
435                                            String modes = sanitizeElement.attributeValue("modes");
436    
437                                            fieldSanitize = new Tuple(fieldName, contentType, modes);
438                                    }
439    
440                                    Map<String, Tuple> fieldValidators = new TreeMap<>();
441    
442                                    fieldElements = fieldElement.elements("validator");
443    
444                                    for (Element validatorElement : fieldElements) {
445                                            String validatorName = validatorElement.attributeValue(
446                                                    "name");
447    
448                                            if (Validator.isNull(validatorName)) {
449                                                    continue;
450                                            }
451    
452                                            String validatorErrorMessage = GetterUtil.getString(
453                                                    validatorElement.attributeValue("error-message"));
454                                            String validatorValue = GetterUtil.getString(
455                                                    validatorElement.getText());
456                                            boolean customValidator = isCustomValidator(validatorName);
457    
458                                            if (customValidator) {
459                                                    validatorName = buildCustomValidatorName(validatorName);
460                                            }
461    
462                                            Tuple fieldValidator = new Tuple(
463                                                    fieldName, validatorName, validatorErrorMessage,
464                                                    validatorValue, customValidator);
465    
466                                            fieldValidators.put(validatorName, fieldValidator);
467                                    }
468    
469                                    fields.put(fieldName + _ELEMENTS_SUFFIX, fieldElement);
470                                    fields.put(fieldName + _TYPE_SUFFIX, fieldType);
471                                    fields.put(fieldName + _LOCALIZATION_SUFFIX, fieldLocalized);
472                                    fields.put(fieldName + _HINTS_SUFFIX, fieldHints);
473    
474                                    if (fieldSanitize != null) {
475                                            fields.put(fieldName + _SANITIZE_SUFFIX, fieldSanitize);
476                                    }
477    
478                                    if (!fieldValidators.isEmpty()) {
479                                            fields.put(
480                                                    fieldName + _VALIDATORS_SUFFIX,
481                                                    ListUtil.fromMapValues(fieldValidators));
482                                    }
483                            }
484                    }
485            }
486    
487            @Override
488            public String trimString(String model, String field, String value) {
489                    if (value == null) {
490                            return value;
491                    }
492    
493                    int maxLength = getMaxLength(model, field);
494    
495                    if (value.length() > maxLength) {
496                            return value.substring(0, maxLength);
497                    }
498                    else {
499                            return value;
500                    }
501            }
502    
503            private static final String _ELEMENTS_SUFFIX = "_ELEMENTS";
504    
505            private static final String _HINTS_SUFFIX = "_HINTS";
506    
507            private static final String _LOCALIZATION_SUFFIX = "_LOCALIZATION";
508    
509            private static final String _SANITIZE_SUFFIX = "_SANITIZE_SUFFIX";
510    
511            private static final String _TYPE_SUFFIX = "_TYPE";
512    
513            private static final String _VALIDATORS_SUFFIX = "_VALIDATORS";
514    
515            private static final Log _log = LogFactoryUtil.getLog(
516                    BaseModelHintsImpl.class);
517    
518            private Map<String, Map<String, String>> _defaultHints;
519            private Map<String, Map<String, String>> _hintCollections;
520            private Map<String, Object> _modelFields;
521            private Set<String> _models;
522    
523    }