001/*
002 * (C) Copyright 2014-2018 Nuxeo (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.lang3.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     * The validation parameter key to enable/disable validation on resolver. Enabled by default.
045     *
046     * @since 10.2
047     */
048    String VALIDATION_PARAMETER_KEY = "validation";
049
050    /**
051     * Configure this resolver.
052     *
053     * @param parameters A map of parameter whose keys are parameter names and map value are corresponding values.
054     * @throws IllegalArgumentException If some parameter are not compatible with this resolver.
055     * @throws IllegalStateException If this resolver is already configured.
056     * @since 7.1
057     */
058    void configure(Map<String, String> parameters) throws IllegalArgumentException, IllegalStateException;
059
060    /**
061     * Returns the resolved object types.
062     *
063     * @since 7.2
064     */
065    List<Class<?>> getManagedClasses();
066
067    /**
068     * Provides this resolver name.
069     *
070     * @return The resolver name.
071     * @since 7.1
072     */
073    String getName();
074
075    /**
076     * Provides this resolver parameters.
077     *
078     * @return A map containing <parameter name , parameter value>
079     * @since 7.1
080     */
081    Map<String, Serializable> getParameters();
082
083    /**
084     * Validates some value references an existing entity.
085     *
086     * @param value The reference.
087     * @return true if value could be resolved as an existing external reference, false otherwise.
088     * @throws IllegalStateException If this resolver has not been configured.
089     * @since 7.1
090     */
091    boolean validate(Object value);
092
093    /**
094     * Validates some value references an existing entity, in the given context
095     *
096     * @param value The reference.
097     * @param context A resolver-specific context allowing resolution of the value.
098     * @return true if value could be resolved as an existing external reference, false otherwise.
099     * @throws IllegalStateException If this resolver has not been configured.
100     * @since 10.2
101     */
102    default boolean validate(Object value, Object context) {
103        return validate(value);
104    }
105
106    /**
107     * Provides the entity referenced by a value.
108     *
109     * @param value The reference.
110     * @return The referenced entity, null if no entity matches the value.
111     * @throws IllegalStateException If this resolver has not been configured.
112     * @since 7.1
113     */
114    Object fetch(Object value);
115
116    /**
117     * Provides the entity referenced by a value, in the given context.
118     *
119     * @param value The reference.
120     * @param context A resolver-specific context allowing resolution of the value.
121     * @return The referenced entity, null if no entity matches the value.
122     * @throws IllegalStateException If this resolver has not been configured.
123     * @since 10.2
124     */
125    default Object fetch(Object value, Object context) {
126        return fetch(value);
127    }
128
129    /**
130     * Provides the entity referenced by a value, return the entity as expected type.
131     *
132     * @param value The reference.
133     * @return The referenced entity, null if no entity matches the value or if this entity cannot be converted as type.
134     * @throws IllegalStateException If this resolver has not been configured.
135     * @since 7.1
136     */
137    <T> T fetch(Class<T> type, Object value);
138
139    /**
140     * Generates a reference to an entity.
141     *
142     * @param object The entity.
143     * @return A reference to the entity or null if its not a managed entity type.
144     * @throws IllegalStateException If this resolver has not been configured.
145     * @since 7.1
146     */
147    Serializable getReference(Object object);
148
149    /**
150     * Provides an error message to display when some invalid value does not match existing entity.
151     *
152     * @param invalidValue The invalid value that don't match any entity.
153     * @param locale The language in which the message should be generated.
154     * @return A message in the specified language or
155     * @since 7.1
156     */
157    String getConstraintErrorMessage(Object invalidValue, Locale locale);
158
159    /**
160     * Manage translation for resolver : {@link #getConstraintErrorMessage(ObjectResolver, Object, Locale, String...)}
161     *
162     * @since 7.1
163     */
164    final class Helper {
165
166        private static final Log log = LogFactory.getLog(Helper.class);
167
168        private Helper() {
169        }
170
171        /**
172         * Use a default translation key : label.schema.constraint.resolver.[Resolver.getName()]
173         *
174         * @param resolver The requesting resolver.
175         * @param suffixCase This field is a which allow to define alternative translation.
176         * @param invalidValue The invalid value that don't match any entity.
177         * @param locale The language in which the message should be generated.
178         * @param additionnalParameters Relayed elements to build the message.
179         * @return A message in the specified language
180         * @since 7.1
181         */
182        public static String getConstraintErrorMessage(ObjectResolver resolver, String suffixCase, Object invalidValue,
183                Locale locale, String... additionnalParameters) {
184            List<String> pathTokens = new ArrayList<>();
185            pathTokens.add(Constraint.MESSAGES_KEY);
186            pathTokens.add("resolver");
187            pathTokens.add(resolver.getName());
188            if (suffixCase != null) {
189                pathTokens.add(suffixCase);
190            }
191            String keyConstraint = StringUtils.join(pathTokens, '.');
192            String computedInvalidValue = "null";
193            if (invalidValue != null) {
194                String invalidValueString = invalidValue.toString();
195                if (invalidValueString.length() > 20) {
196                    computedInvalidValue = invalidValueString.substring(0, 15) + "...";
197                } else {
198                    computedInvalidValue = invalidValueString;
199                }
200            }
201            Object[] params = new Object[1 + additionnalParameters.length];
202            params[0] = computedInvalidValue;
203            System.arraycopy(additionnalParameters, 0, params, 1, params.length - 1);
204            Locale computedLocale = locale != null ? locale : Constraint.MESSAGES_DEFAULT_LANG;
205            String message;
206            try {
207                message = I18NUtils.getMessageString(Constraint.MESSAGES_BUNDLE, keyConstraint, params, computedLocale);
208            } catch (MissingResourceException e) {
209                log.trace("No bundle found", e);
210                return null;
211            }
212            if (message != null && !message.trim().isEmpty() && !keyConstraint.equals(message)) {
213                // use a constraint specific message if there's one
214                return message;
215            } else {
216                return String.format("%s cannot resolve reference %s", resolver.getName(), computedInvalidValue);
217            }
218        }
219
220        /**
221         * Use a default translation key : label.schema.constraint.resolver.[Resolver.getName()]
222         *
223         * @param resolver The requesting resolver.
224         * @param invalidValue The invalid value that don't match any entity.
225         * @param locale The language in which the message should be generated.
226         * @param additionnalParameters Relayed elements to build the message.
227         * @return A message in the specified language
228         * @since 7.1
229         */
230        public static String getConstraintErrorMessage(ObjectResolver resolver, Object invalidValue, Locale locale,
231                String... additionnalParameters) {
232            return Helper.getConstraintErrorMessage(resolver, null, invalidValue, locale, additionnalParameters);
233        }
234    }
235
236}