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