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