001/**
002 * Copyright 2005-2018 The Kuali Foundation
003 *
004 * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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 */
016package org.kuali.rice.krad.datadictionary.validation.constraint;
017
018import org.kuali.rice.krad.datadictionary.DictionaryBeanBase;
019import org.kuali.rice.krad.datadictionary.parse.BeanTag;
020import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
021import org.kuali.rice.krad.datadictionary.validator.ErrorReport;
022import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
023
024import java.util.ArrayList;
025import java.util.List;
026
027/**
028 * A class that implements the required accessor for label keys. This provides a convenient base class
029 * from which other constraints can be derived.
030 *
031 * Only BaseConstraints can have state validation.
032 *
033 * This class is a direct copy of one that was in Kuali Student.
034 *
035 * @author Kuali Rice Team (rice.collab@kuali.org)
036 * @since 1.1
037 */
038@BeanTag(name = "constraint")
039public class BaseConstraint extends DictionaryBeanBase implements Constraint {
040    private static final long serialVersionUID = -2891712660500311114L;
041
042    protected String messageNamespaceCode;
043    protected String messageComponentCode;
044    protected String messageKey;
045
046    protected Boolean applyClientSide;
047
048    protected List<String> validationMessageParams;
049    protected List<String> states;
050    protected List<? extends BaseConstraint> constraintStateOverrides;
051
052    public BaseConstraint() {
053        applyClientSide = Boolean.valueOf(true);
054    }
055
056    /**
057     * Namespace code (often an application or module code) the constraint failure message is associated with
058     *
059     * <p>
060     * Used with the component code and error key for retrieving the constraint. If null,
061     * the default namespace code will be used
062     * </p>
063     *
064     * @return String constraint message namespace code
065     */
066    @BeanTagAttribute(name = "messageNamespaceCode")
067    public String getMessageNamespaceCode() {
068        return messageNamespaceCode;
069    }
070
071    /**
072     * Setter for the constraint message associated namespace code
073     *
074     * @param messageNamespaceCode
075     */
076    public void setMessageNamespaceCode(String messageNamespaceCode) {
077        this.messageNamespaceCode = messageNamespaceCode;
078    }
079
080    /**
081     * A code within the namespace that identifies a component or group the constraint message is associated with
082     *
083     * <p>
084     * Used with the namespace and error key for retrieving the constraint text. If null,
085     * the default component code will be used
086     * </p>
087     *
088     * @return String message component code
089     */
090    @BeanTagAttribute(name = "messageComponentCode")
091    public String getMessageComponentCode() {
092        return messageComponentCode;
093    }
094
095    /**
096     * Setter for the constraint message associated component code
097     *
098     * @param messageComponentCode
099     */
100    public void setMessageComponentCode(String messageComponentCode) {
101        this.messageComponentCode = messageComponentCode;
102    }
103
104    /**
105     * A key that is used to retrieve the constraint message text (used with the namespace and component
106     * code if specified)
107     *
108     * @return String message key
109     */
110    @BeanTagAttribute(name = "messageKey")
111    public String getMessageKey() {
112        return messageKey;
113    }
114
115    /**
116     * Setter for the constraint message key
117     *
118     * @param messageKey
119     */
120    public void setMessageKey(String messageKey) {
121        this.messageKey = messageKey;
122    }
123
124    /**
125     * If this is true, the constraint should be applied on the client side when the user interacts with
126     * a field - if this constraint can be interpreted for client side use. Default is true.
127     *
128     * @return the applyClientSide
129     */
130    @BeanTagAttribute(name = "applyClientSide")
131    public Boolean getApplyClientSide() {
132        return this.applyClientSide;
133    }
134
135    /**
136     * @param applyClientSide the applyClientSide to set
137     */
138    public void setApplyClientSide(Boolean applyClientSide) {
139        this.applyClientSide = applyClientSide;
140    }
141
142    /**
143     * Parameters to be used in the string retrieved by this constraint's messageKey, ordered by number of
144     * the param
145     *
146     * @return the validationMessageParams
147     */
148    @BeanTagAttribute(name = "validationMessageParams", type = BeanTagAttribute.AttributeType.LISTVALUE)
149    public List<String> getValidationMessageParams() {
150        return this.validationMessageParams;
151    }
152
153    /**
154     * Parameters to be used in the string retrieved by this constraint's messageKey, ordered by number of
155     * the param
156     *
157     * @return the validationMessageParams
158     */
159    public String[] getValidationMessageParamsArray() {
160        if (this.getValidationMessageParams() != null) {
161            return this.getValidationMessageParams().toArray(new String[this.getValidationMessageParams().size()]);
162        } else {
163            return null;
164        }
165
166    }
167
168    /**
169     * @param validationMessageParams the validationMessageParams to set
170     */
171    public void setValidationMessageParams(List<String> validationMessageParams) {
172        this.validationMessageParams = validationMessageParams;
173    }
174
175    /**
176     * A list of states to apply this constraint for, this will effect when a constraint
177     * is applied.
178     *
179     * <p>Each state this constraint is applied for needs to be declared with few additional options:
180     * <ul>
181     * <li>if NO states are defined for this constraint, this constraint is applied for ALL states</li>
182     * <li>if a state is defined with a + symbol, example "state+", then this constraint will be applied for that state
183     * and ALL following states</li>
184     * <li>if a state is defined as a range with ">", example "state1>state6", then this constraint will be applied for
185     * all
186     * states from state1 to state6 </li>
187     * </ul>
188     * These can be mixed and matched, as appropriate, though states using a + symbol should always be the last
189     * item of a list (as they imply this state and everything else after).</p>
190     *
191     * <p>Example state list may be: ["state1", "state3>state5", "state6+"].  In this example, note that this
192     * constraint
193     * is never applied to "state2" (assuming these example states represent a state order by number)</p>
194     *
195     * @return the states to apply the constraint on, an empty list if the constraint is applied for all states
196     */
197    @BeanTagAttribute(name = "states", type = BeanTagAttribute.AttributeType.LISTVALUE)
198    public List<String> getStates() {
199        if (states == null) {
200            states = new ArrayList<String>();
201        }
202        return states;
203    }
204
205    /**
206     * Set the states for this contraint to be applied on
207     *
208     * @param states
209     */
210    public void setStates(List<String> states) {
211        this.states = states;
212    }
213
214    /**
215     * Get the list of constraintStateOverrides which represent constraints that will replace THIS constraint
216     * when their state is matched during validation.
217     * Because of this, constraints added to this list MUST have their states defined.
218     *
219     * <p>ConstraintStateOverrides always take precedence over this
220     * constraint if they apply to the state being evaluated during validation.  These settings have no effect if
221     * there is no stateMapping represented on the entry/view being evaluated.
222     * </p>
223     *
224     * @return List of constraint overrides for this constraint
225     */
226    @BeanTagAttribute(name = "constraintStateOverrides", type = BeanTagAttribute.AttributeType.LISTBEAN)
227    public List<? extends BaseConstraint> getConstraintStateOverrides() {
228        return constraintStateOverrides;
229    }
230
231    /**
232     * Set the constraintStateOverrides to be used when a state is matched during validation
233     *
234     * @param constraintStateOverrides
235     */
236    public void setConstraintStateOverrides(List<? extends BaseConstraint> constraintStateOverrides) {
237        if (constraintStateOverrides != null) {
238            for (BaseConstraint bc : constraintStateOverrides) {
239                if (!bc.getClass().equals(this.getClass())) {
240                    List<Class<?>> superClasses = new ArrayList<Class<?>>();
241                    Class<?> o = bc.getClass();
242                    while (o != null && !o.equals(BaseConstraint.class)) {
243                        superClasses.add(o);
244                        o = o.getSuperclass();
245                    }
246
247                    List<Class<?>> thisSuperClasses = new ArrayList<Class<?>>();
248                    o = this.getClass();
249                    while (o != null && !o.equals(BaseConstraint.class)) {
250                        thisSuperClasses.add(o);
251                        o = o.getSuperclass();
252                    }
253                    superClasses.retainAll(thisSuperClasses);
254
255                    if (superClasses.isEmpty()) {
256                        throw new RuntimeException("Constraint State Override is not a correct type, type should be " +
257                                this.getClass().toString() + " (or child/parent of that constraint type)");
258                    }
259                }
260                if (bc.getStates().isEmpty()) {
261                    throw new RuntimeException(
262                            "Constraint State Overrides MUST declare the states they apply to.  No states"
263                                    + "were declared.");
264                }
265            }
266        }
267        this.constraintStateOverrides = constraintStateOverrides;
268    }
269
270
271
272    /**
273     * Validates different requirements of component compiling a series of reports detailing information on errors
274     * found in the component.  Used by the RiceDictionaryValidator.
275     *
276     * @param tracer Record of component's location
277     */
278    public void completeValidation(ValidationTrace tracer) {
279        tracer.addBean("BaseConstraint", getMessageKey());
280
281        if (getConstraintStateOverrides() != null) {
282            for (int i = 0; i < constraintStateOverrides.size(); i++) {
283                if (constraintStateOverrides.get(i).getStates() == null) {
284                    String currentValues[] =
285                            {"constraintStateOverrides(" + i + ").messageKey =" + constraintStateOverrides.get(i)
286                                    .getMessageKey()};
287                    tracer.createError("Constraints set in State Overrides must have there states property set",
288                            currentValues);
289                }
290                constraintStateOverrides.get(i).completeValidation(tracer.getCopy());
291            }
292        }
293
294        if (getMessageKey() == null) {
295            String currentValues[] = {"messageKey =" + getMessageKey()};
296            tracer.createWarning("Message key is not set", currentValues);
297            ErrorReport error = new ErrorReport(ErrorReport.WARNING);
298        }
299    }
300}