001/*
002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nicolas Chapurlat <nchapurlat@nuxeo.com>
016 */
017
018package org.nuxeo.ecm.core.schema.types.resolver;
019
020import java.io.Serializable;
021import java.util.ArrayList;
022import java.util.List;
023import java.util.Locale;
024import java.util.Map;
025import java.util.MissingResourceException;
026
027import org.apache.commons.lang.StringUtils;
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030import org.nuxeo.common.utils.i18n.I18NUtils;
031import org.nuxeo.ecm.core.schema.types.constraints.Constraint;
032
033/**
034 * External references are document field with a simple type whose value refers to an external business entity. Objects
035 * implementing this interface are able to resolve the entity using the reference.
036 *
037 * @since 7.1
038 */
039public interface ObjectResolver extends Serializable {
040
041    /**
042     * Configure this resolver.
043     *
044     * @param parameters A map of parameter whose keys are parameter names and map value are corresponding values.
045     * @throws IllegalArgumentException If some parameter are not compatible with this resolver.
046     * @throws IllegalStateException If this resolver is already configured.
047     * @since 7.1
048     */
049    void configure(Map<String, String> parameters) throws IllegalArgumentException, IllegalArgumentException;
050
051    /**
052     * Returns the resolved object types.
053     *
054     * @since 7.2
055     */
056    List<Class<?>> getManagedClasses();
057
058    /**
059     * Provides this resolver name.
060     *
061     * @return The resolver name.
062     * @since 7.1
063     */
064    String getName();
065
066    /**
067     * Provides this resolver parameters.
068     *
069     * @return A map containing <parameter name , parameter value>
070     * @since 7.1
071     */
072    Map<String, Serializable> getParameters();
073
074    /**
075     * Validates some value references an existing entity.
076     *
077     * @param value The reference.
078     * @return true if value could be resolved as an existing external reference, false otherwise.
079     * @throws IllegalStateException If this resolver has not been configured.
080     * @since 7.1
081     */
082    boolean validate(Object value);
083
084    /**
085     * Provides the entity referenced by a value.
086     *
087     * @param value The reference.
088     * @return The referenced entity, null if no entity matches the value.
089     * @throws IllegalStateException If this resolver has not been configured.
090     * @since 7.1
091     */
092    Object fetch(Object value);
093
094    /**
095     * Provides the entity referenced by a value, return the entity as expected type.
096     *
097     * @param value The reference.
098     * @return The referenced entity, null if no entity matches the value or if this entity cannot be converted as type.
099     * @throws IllegalStateException If this resolver has not been configured.
100     * @since 7.1
101     */
102    <T> T fetch(Class<T> type, Object value);
103
104    /**
105     * Generates a reference to an entity.
106     *
107     * @param object The entity.
108     * @return A reference to the entity or null if its not a managed entity type.
109     * @throws IllegalStateException If this resolver has not been configured.
110     * @since 7.1
111     */
112    Serializable getReference(Object object);
113
114    /**
115     * Provides an error message to display when some invalid value does not match existing entity.
116     *
117     * @param invalidValue The invalid value that don't match any entity.
118     * @param locale The language in which the message should be generated.
119     * @return A message in the specified language or
120     * @since 7.1
121     */
122    String getConstraintErrorMessage(Object invalidValue, Locale locale);
123
124    /**
125     * Manage translation for resolver : {@link #getConstraintErrorMessage(ObjectResolver, Object, Locale)}
126     *
127     * @since 7.1
128     */
129    public static final class Helper {
130
131        private static final Log log = LogFactory.getLog(Helper.class);
132
133        private Helper() {
134        }
135
136        /**
137         * Use a default translation key : label.schema.constraint.resolver.[Resolver.getName()]
138         *
139         * @param resolver The requesting resolver.
140         * @param suffixCase This field is a which allow to define alternative translation.
141         * @param invalidValue The invalid value that don't match any entity.
142         * @param locale The language in which the message should be generated.
143         * @param additionnalParameters Relayed elements to build the message.
144         * @return A message in the specified language
145         * @since 7.1
146         */
147        public static String getConstraintErrorMessage(ObjectResolver resolver, String suffixCase, Object invalidValue,
148                Locale locale, String... additionnalParameters) {
149            List<String> pathTokens = new ArrayList<String>();
150            pathTokens.add(Constraint.MESSAGES_KEY);
151            pathTokens.add("resolver");
152            pathTokens.add(resolver.getName());
153            if (suffixCase != null) {
154                pathTokens.add(suffixCase);
155            }
156            String keyConstraint = StringUtils.join(pathTokens, '.');
157            String computedInvalidValue = "null";
158            if (invalidValue != null) {
159                String invalidValueString = invalidValue.toString();
160                if (invalidValueString.length() > 20) {
161                    computedInvalidValue = invalidValueString.substring(0, 15) + "...";
162                } else {
163                    computedInvalidValue = invalidValueString;
164                }
165            }
166            Object[] params = new Object[1 + additionnalParameters.length];
167            params[0] = computedInvalidValue;
168            for (int i = 1; i < params.length; i++) {
169                params[i] = additionnalParameters[i - 1];
170            }
171            Locale computedLocale = locale != null ? locale : Constraint.MESSAGES_DEFAULT_LANG;
172            String message;
173            try {
174                message = I18NUtils.getMessageString(Constraint.MESSAGES_BUNDLE, keyConstraint, params, computedLocale);
175            } catch (MissingResourceException e) {
176                log.trace("No bundle found", e);
177                return null;
178            }
179            if (message != null && !message.trim().isEmpty() && !keyConstraint.equals(message)) {
180                // use a constraint specific message if there's one
181                return message;
182            } else {
183                return String.format("%s cannot resolve reference %s", resolver.getName(), computedInvalidValue);
184            }
185        }
186
187        /**
188         * Use a default translation key : label.schema.constraint.resolver.[Resolver.getName()]
189         *
190         * @param resolver The requesting resolver.
191         * @param invalidValue The invalid value that don't match any entity.
192         * @param locale The language in which the message should be generated.
193         * @param additionnalParameters Relayed elements to build the message.
194         * @return A message in the specified language
195         * @since 7.1
196         */
197        public static String getConstraintErrorMessage(ObjectResolver resolver, Object invalidValue, Locale locale,
198                String... additionnalParameters) {
199            return Helper.getConstraintErrorMessage(resolver, null, invalidValue, locale, additionnalParameters);
200        }
201    }
202
203}