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}