001/*
002* The contents of this file are subject to the terms of the Common Development and
003* Distribution License (the License). You may not use this file except in compliance with the
004* License.
005*
006* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007* specific language governing permission and limitations under the License.
008*
009* When distributing Covered Software, include this CDDL Header Notice in each file and include
010* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011* Header, with the fields enclosed by brackets [] replaced by your own identifying
012* information: "Portions copyright [year] [name of copyright owner]".
013*
014* Copyright 2014-2015 ForgeRock AS.
015*/
016
017package org.forgerock.json.resource;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.Collection;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.TreeMap;
026import java.util.regex.Pattern;
027
028import org.forgerock.services.context.AbstractContext;
029import org.forgerock.services.context.Context;
030import org.forgerock.json.JsonValue;
031import org.forgerock.util.Reject;
032
033/**
034 * A {@link Context} containing information which should be returned to the user in some
035 * appropriate form to the user. For example, it could be contained within the body of the response
036 * or otherwise added to the headers returned.
037 *
038 * @since 2.4.0
039 */
040public class AdviceContext extends AbstractContext {
041
042    /** the persisted attribute name for the advices. */
043    private static final String ADVICE_ATTR = "advice";
044
045    /** The persisted attribute name for the restricted advice names. */
046    private static final String RESTRICTED_ADVICE_NAMES_ATTR = "restrictedAdviceNames";
047
048    private static final Pattern ALLOWED_RFC_CHARACTERS = Pattern.compile("^[\\x20-\\x7E]*$");
049
050    private final Collection<String> restrictedAdviceNames = new HashSet<>();
051
052    /** Advice currently stored for this context is help in this map. **/
053    private final Map<String, List<String>> advice = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
054
055    /**
056     * Creates a new AdviceContext with the provided parent.
057     *
058     * @param parent The parent context.
059     * @param restrictedAdviceNames The restricted advice names.
060     */
061    public AdviceContext(Context parent, Collection<String> restrictedAdviceNames) {
062        super(parent, "advice");
063        this.restrictedAdviceNames.addAll(restrictedAdviceNames);
064        data.put(RESTRICTED_ADVICE_NAMES_ATTR, restrictedAdviceNames);
065        data.put(ADVICE_ATTR, advice);
066    }
067
068    /**
069     * Restore from JSON representation.
070     *
071     * @param savedContext
072     *            The JSON representation from which this context's attributes
073     *            should be parsed.
074     * @param classLoader
075     *            The ClassLoader which can properly resolve the persisted class-name.
076     */
077    public AdviceContext(final JsonValue savedContext, final ClassLoader classLoader) {
078        super(savedContext, classLoader);
079        restrictedAdviceNames.addAll(data.get(RESTRICTED_ADVICE_NAMES_ATTR).asSet(String.class));
080        advice.putAll(data.get(ADVICE_ATTR).asMapOfList(String.class));
081    }
082
083    /**
084     * Returns the advices contained within this context.
085     *
086     * @return the advices contained within this context.
087     */
088    public Map<String, List<String>> getAdvices() {
089        return advice;
090    }
091
092    /**
093     * Adds advice to the context, which can be retrieved and later returned to the user.
094     *
095     * @param adviceName Name of the advice to return to the user. Not null.
096     * @param advices Human-readable advice to return to the user. Not null.
097     */
098    public void putAdvice(String adviceName, String... advices) {
099        Reject.ifNull(adviceName, advices);
100        Reject.ifTrue(isRestrictedAdvice(adviceName), "Illegal use of restricted advice name, " + adviceName);
101        for (String adviceEntry : advices) {
102            Reject.ifTrue(!isRfcCompliant(adviceEntry), "Advice contains illegal characters in, " + adviceEntry);
103        }
104        List<String> adviceEntry = advice.get(adviceName);
105        if (adviceEntry == null) {
106            adviceEntry = new ArrayList<>();
107            advice.put(adviceName, adviceEntry);
108        }
109        adviceEntry.addAll(Arrays.asList(advices));
110    }
111
112    private boolean isRfcCompliant(String advice) {
113        return ALLOWED_RFC_CHARACTERS.matcher(advice).matches();
114    }
115
116    private boolean isRestrictedAdvice(String adviceName) {
117        return restrictedAdviceNames.contains(adviceName);
118    }
119}