001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau; 018 019import static java.util.Collections.*; 020import static org.apache.juneau.commons.utils.AssertionUtils.*; 021import static org.apache.juneau.commons.utils.CollectionUtils.*; 022import static org.apache.juneau.commons.utils.StringUtils.*; 023import static org.apache.juneau.commons.utils.ThrowableUtils.*; 024import static org.apache.juneau.commons.utils.Utils.*; 025 026import java.text.*; 027import java.util.*; 028import java.util.function.*; 029 030import org.apache.juneau.commons.collections.*; 031import org.apache.juneau.commons.function.*; 032import org.apache.juneau.commons.reflect.*; 033 034/** 035 * A one-time-use non-thread-safe object that's meant to be used once and then thrown away. 036 * 037 * <h5 class='section'>Notes:</h5><ul> 038 * <li class='warn'>This class is not typically thread safe. 039 * </ul> 040 * 041 */ 042public abstract class ContextSession { 043 044 /** 045 * Builder class. 046 */ 047 public static abstract class Builder { 048 private Boolean debug; 049 private boolean unmodifiable; 050 private Context ctx; 051 private ResettableSupplier<LinkedHashMap<String,Object>> properties; 052 053 /** 054 * Constructor. 055 * 056 * @param ctx The context creating this session. 057 * <br>Cannot be <jk>null</jk>. 058 */ 059 protected Builder(Context ctx) { 060 this.ctx = assertArgNotNull("ctx", ctx); 061 this.properties = memr(LinkedHashMap::new); 062 } 063 064 /** 065 * Applies a consumer to this builder if it's the specified type. 066 * 067 * @param <T> The expected type. 068 * @param type The expected type. 069 * <br>Cannot be <jk>null</jk>. 070 * @param apply The consumer to apply. 071 * <br>Cannot be <jk>null</jk>. 072 * @return This object. 073 */ 074 public <T> Builder apply(Class<T> type, Consumer<T> apply) { 075 if (assertArgNotNull("type", type).isInstance(this)) 076 assertArgNotNull("apply", apply).accept(type.cast(this)); 077 return this; 078 } 079 080 /** 081 * Build the object. 082 * 083 * @return The built object. 084 */ 085 public abstract ContextSession build(); 086 087 /** 088 * Debug mode. 089 * 090 * <p> 091 * Enables the following additional information during parsing: 092 * <ul> 093 * <li> When bean setters throws exceptions, the exception includes the object stack information in order to determine how that method was invoked. 094 * </ul> 095 * 096 * <p> 097 * If not specified, defaults to {@link Context.Builder#debug()}. 098 * 099 * <h5 class='section'>See Also:</h5><ul> 100 * <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#debug()} 101 * <li class='jm'>{@link org.apache.juneau.Context.Builder#debug()} 102 * 103 * @param value 104 * The new value for this property. 105 * <br>If <jk>null</jk>, defaults to {@link Context#isDebug()}. 106 * @return This object. 107 */ 108 public Builder debug(Boolean value) { 109 debug = value; 110 return this; 111 } 112 113 /** 114 * Session properties. 115 * 116 * <p> 117 * Session properties are generic key-value pairs that can be passed through the session and made 118 * available to any customized serializers/parsers or swaps. 119 * 120 * @param value 121 * The new value for this property. 122 * <br>Cannot be <jk>null</jk>. 123 * @return This object. 124 */ 125 public Builder properties(Map<String,Object> value) { 126 assertArgNotNull("value", value); 127 properties.reset(); 128 properties.get().putAll(value); 129 return this; 130 } 131 132 /** 133 * Adds a property to this session. 134 * 135 * @param key The property key. 136 * <br>Cannot be <jk>null</jk>. 137 * @param value The property value. 138 * <br>Can be <jk>null</jk> (removes the property). 139 * @return This object. 140 */ 141 public Builder property(String key, Object value) { 142 assertArgNotNull("key", key); 143 var map = properties.get(); 144 if (value == null) { 145 map.remove(key); 146 } else { 147 map.put(key, value); 148 } 149 return this; 150 } 151 152 /** 153 * Create an unmodifiable session. 154 * 155 * <p> 156 * The created ContextSession object will be unmodifiable which makes it suitable for caching and reuse. 157 * 158 * @return This object. 159 */ 160 public Builder unmodifiable() { 161 unmodifiable = true; 162 return this; 163 } 164 } 165 166 private final boolean debug; 167 private final boolean unmodifiable; 168 private final Context ctx; 169 private final Map<String,Object> properties; 170 private List<String> warnings; // Any warnings encountered. 171 172 /** 173 * Default constructor. 174 * 175 * @param builder The builder for this object. 176 * <br>Cannot be <jk>null</jk>. 177 */ 178 protected ContextSession(Builder builder) { 179 assertArgNotNull("builder", builder); 180 ctx = builder.ctx; 181 debug = opt(builder.debug).orElse(ctx.isDebug()); 182 unmodifiable = builder.unmodifiable; 183 var sp = builder.properties.get(); 184 if (unmodifiable) { 185 properties = sp.isEmpty() ? Collections.emptyMap() : u(sp); 186 } else { 187 properties = sp; 188 } 189 } 190 191 /** 192 * Logs a warning message. 193 * 194 * @param msg The warning message. 195 * <br>Cannot be <jk>null</jk>. 196 * @param args Optional {@link MessageFormat}-style arguments. 197 * <br>Cannot contain <jk>null</jk> values. 198 */ 199 public void addWarning(String msg, Object...args) { 200 assertArgsNotNull("msg", msg, "args", args); 201 if (unmodifiable) 202 return; 203 if (warnings == null) 204 warnings = new LinkedList<>(); 205 warnings.add((warnings.size() + 1) + ": " + f(msg, args)); 206 } 207 208 /** 209 * Throws a {@link BeanRuntimeException} if any warnings occurred in this session and debug is enabled. 210 */ 211 public void checkForWarnings() { 212 if (debug && ! getWarnings().isEmpty()) 213 throw bex("Warnings occurred in session: \n" + join(getWarnings(), "\n")); 214 } 215 216 /** 217 * Returns the context that created this session. 218 * 219 * @return The context that created this session. 220 */ 221 public Context getContext() { return ctx; } 222 223 /** 224 * Returns the session properties on this session. 225 * 226 * @return The session properties on this session. Never <jk>null</jk>. 227 */ 228 public final Map<String,Object> getSessionProperties() { return properties; } 229 230 /** 231 * Returns the warnings that occurred in this session. 232 * 233 * @return The warnings that occurred in this session, or <jk>null</jk> if no warnings occurred. 234 */ 235 public final List<String> getWarnings() { return warnings == null ? emptyList() : warnings; } 236 237 /** 238 * Debug mode enabled. 239 * 240 * @see Context.Builder#debug() 241 * @return 242 * <jk>true</jk> if debug mode is enabled. 243 */ 244 public boolean isDebug() { return debug; } 245 246 @Override /* Overridden from Object */ 247 public String toString() { 248 return r(properties()); 249 } 250 251 /** 252 * Returns the properties on this bean as a map for debugging. 253 * 254 * @return The properties on this bean as a map for debugging. 255 */ 256 protected FluentMap<String,Object> properties() { 257 return filteredBeanPropertyMap() 258 .a("debug", debug); 259 } 260}