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