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-2016 ForgeRock AS.
015*/
016
017package org.forgerock.json.resource;
018
019import static org.forgerock.json.JsonValueFunctions.setOf;
020
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Collection;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Map;
027import java.util.TreeMap;
028import java.util.regex.Pattern;
029
030import org.forgerock.services.context.AbstractContext;
031import org.forgerock.services.context.Context;
032import org.forgerock.json.JsonValue;
033import org.forgerock.util.Reject;
034
035/**
036 * A {@link Context} containing information which should be returned to the user in some
037 * appropriate form to the user. For example, it could be contained within the body of the response
038 * or otherwise added to the headers returned.
039 *
040 * @since 2.4.0
041 */
042public class AdviceContext extends AbstractContext {
043
044    /** the persisted attribute name for the advices. */
045    private static final String ADVICE_ATTR = "advice";
046
047    /** The persisted attribute name for the restricted advice names. */
048    private static final String RESTRICTED_ADVICE_NAMES_ATTR = "restrictedAdviceNames";
049
050    private static final Pattern ALLOWED_RFC_CHARACTERS = Pattern.compile("^[\\x20-\\x7E]*$");
051
052    private final Collection<String> restrictedAdviceNames = new HashSet<>();
053
054    /** Advice currently stored for this context is help in this map. **/
055    private final Map<String, List<String>> advice = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
056
057    /**
058     * Creates a new AdviceContext with the provided parent.
059     *
060     * @param parent The parent context.
061     * @param restrictedAdviceNames The restricted advice names.
062     */
063    public AdviceContext(Context parent, Collection<String> restrictedAdviceNames) {
064        super(parent, "advice");
065        this.restrictedAdviceNames.addAll(restrictedAdviceNames);
066        data.put(RESTRICTED_ADVICE_NAMES_ATTR, restrictedAdviceNames);
067        data.put(ADVICE_ATTR, advice);
068    }
069
070    /**
071     * Restore from JSON representation.
072     *
073     * @param savedContext
074     *            The JSON representation from which this context's attributes
075     *            should be parsed.
076     * @param classLoader
077     *            The ClassLoader which can properly resolve the persisted class-name.
078     */
079    public AdviceContext(final JsonValue savedContext, final ClassLoader classLoader) {
080        super(savedContext, classLoader);
081        restrictedAdviceNames.addAll(data.get(RESTRICTED_ADVICE_NAMES_ATTR).as(setOf(String.class)));
082        advice.putAll(data.get(ADVICE_ATTR).asMapOfList(String.class));
083    }
084
085    /**
086     * Returns the advices contained within this context.
087     *
088     * @return the advices contained within this context.
089     */
090    public Map<String, List<String>> getAdvices() {
091        return advice;
092    }
093
094    /**
095     * Adds advice to the context, which can be retrieved and later returned to the user.
096     *
097     * @param adviceName Name of the advice to return to the user. Not null.
098     * @param advices Human-readable advice to return to the user. Not null.
099     */
100    public void putAdvice(String adviceName, String... advices) {
101        Reject.ifNull(adviceName, advices);
102        Reject.ifTrue(isRestrictedAdvice(adviceName), "Illegal use of restricted advice name, " + adviceName);
103        for (String adviceEntry : advices) {
104            Reject.ifTrue(!isRfcCompliant(adviceEntry), "Advice contains illegal characters in, " + adviceEntry);
105        }
106        List<String> adviceEntry = advice.get(adviceName);
107        if (adviceEntry == null) {
108            adviceEntry = new ArrayList<>();
109            advice.put(adviceName, adviceEntry);
110        }
111        adviceEntry.addAll(Arrays.asList(advices));
112    }
113
114    private boolean isRfcCompliant(String advice) {
115        return ALLOWED_RFC_CHARACTERS.matcher(advice).matches();
116    }
117
118    private boolean isRestrictedAdvice(String adviceName) {
119        return restrictedAdviceNames.contains(adviceName);
120    }
121}