View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau;
18  
19  import static java.util.Collections.*;
20  import static org.apache.juneau.commons.utils.AssertionUtils.*;
21  import static org.apache.juneau.commons.utils.CollectionUtils.*;
22  import static org.apache.juneau.commons.utils.StringUtils.*;
23  import static org.apache.juneau.commons.utils.ThrowableUtils.*;
24  import static org.apache.juneau.commons.utils.Utils.*;
25  
26  import java.text.*;
27  import java.util.*;
28  import java.util.function.*;
29  
30  import org.apache.juneau.commons.collections.*;
31  import org.apache.juneau.commons.function.*;
32  import org.apache.juneau.commons.reflect.*;
33  
34  /**
35   * A one-time-use non-thread-safe object that's meant to be used once and then thrown away.
36   *
37   * <h5 class='section'>Notes:</h5><ul>
38   * 	<li class='warn'>This class is not typically thread safe.
39   * </ul>
40   *
41   */
42  public abstract class ContextSession {
43  
44  	/**
45  	 * Builder class.
46  	 */
47  	public static abstract class Builder {
48  		private Boolean debug;
49  		private boolean unmodifiable;
50  		private Context ctx;
51  		private ResettableSupplier<LinkedHashMap<String,Object>> properties;
52  
53  		/**
54  		 * Constructor.
55  		 *
56  		 * @param ctx The context creating this session.
57  		 * 	<br>Cannot be <jk>null</jk>.
58  		 */
59  		protected Builder(Context ctx) {
60  			this.ctx = assertArgNotNull("ctx", ctx);
61  			this.properties = memr(LinkedHashMap::new);
62  		}
63  
64  		/**
65  		 * Applies a consumer to this builder if it's the specified type.
66  		 *
67  		 * @param <T> The expected type.
68  		 * @param type The expected type.
69  		 * 	<br>Cannot be <jk>null</jk>.
70  		 * @param apply	The consumer to apply.
71  		 * 	<br>Cannot be <jk>null</jk>.
72  		 * @return This object.
73  		 */
74  		public <T> Builder apply(Class<T> type, Consumer<T> apply) {
75  			if (assertArgNotNull("type", type).isInstance(this))
76  				assertArgNotNull("apply", apply).accept(type.cast(this));
77  			return this;
78  		}
79  
80  		/**
81  		 * Build the object.
82  		 *
83  		 * @return The built object.
84  		 */
85  		public abstract ContextSession build();
86  
87  		/**
88  		 * Debug mode.
89  		 *
90  		 * <p>
91  		 * Enables the following additional information during parsing:
92  		 * <ul>
93  		 * 	<li> When bean setters throws exceptions, the exception includes the object stack information in order to determine how that method was invoked.
94  		 * </ul>
95  		 *
96  		 * <p>
97  		 * If not specified, defaults to {@link Context.Builder#debug()}.
98  		 *
99  		 * <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 }