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}