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