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 }