001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau;
014
015import static org.apache.juneau.internal.StringUtils.*;
016import static org.apache.juneau.internal.ClassUtils.*;
017import static org.apache.juneau.Context.*;
018
019import java.lang.reflect.*;
020import java.text.*;
021import java.time.*;
022import java.util.*;
023
024import org.apache.juneau.collections.*;
025import org.apache.juneau.http.*;
026import org.apache.juneau.internal.*;
027import org.apache.juneau.json.*;
028import org.apache.juneau.reflect.*;
029
030/**
031 * A one-time-use non-thread-safe object that's meant to be used once and then thrown away.
032 *
033 * <p>
034 * Serializers and parsers use session objects to retrieve config properties and to use it as a scratchpad during
035 * serialize and parse actions.
036 */
037public abstract class Session {
038
039   private final OMap properties;
040   private Map<String,Object> cache;
041   private List<String> warnings;                 // Any warnings encountered.
042
043   private final boolean debug;
044   private final Locale locale;
045   private final TimeZone timeZone;
046   private final MediaType mediaType;
047
048
049   /**
050    * Default constructor.
051    *
052    * @param ctx The context object.
053    * @param args
054    *    Runtime arguments.
055    */
056   protected Session(Context ctx, SessionArgs args) {
057      this.properties = args.properties == null ? OMap.EMPTY_MAP : args.properties;
058      debug = getProperty(CONTEXT_debug, Boolean.class, ctx.isDebug());
059      locale = getProperty(CONTEXT_locale, Locale.class, ctx.getDefaultLocale());
060      timeZone = getProperty(CONTEXT_timeZone, TimeZone.class, ctx.getDefaultTimeZone());
061      mediaType = getProperty(CONTEXT_mediaType, MediaType.class, ctx.getDefaultMediaType());
062   }
063
064   /**
065    * Returns <jk>true</jk> if this session has the specified property defined.
066    *
067    * @param key The property key.
068    * @return <jk>true</jk> if this session has the specified property defined.
069    */
070   public final boolean hasProperty(String key) {
071      return properties != null && properties.containsKey(key);
072   }
073
074   /**
075    * Returns the session property with the specified key.
076    *
077    * <p>
078    * The returned type is the raw value of the property.
079    *
080    * @param key The property key.
081    * @return The session property, or <jk>null</jk> if the property does not exist.
082    */
083   public final Object getProperty(String key) {
084      if (properties == null)
085         return null;
086      return properties.get(key);
087   }
088
089   /**
090    * Returns the session property with the specified key and type.
091    *
092    * @param key The property key.
093    * @param type The type to convert the property to.
094    * @param def The default value if the session property does not exist or is <jk>null</jk>.
095    * @return The session property.
096    */
097   @SuppressWarnings("unchecked")
098   public final <T> T getProperty(String key, Class<T> type, T def) {
099      if (properties == null)
100         return def;
101      Object o = properties.get(key);
102      if (o == null)
103         return def;
104      type = (Class<T>)ClassInfo.of(type).getWrapperIfPrimitive();
105      T t = properties.get(key, type);
106      return t == null ? def : t;
107   }
108
109   /**
110    * Same as {@link #getProperty(String, Class, Object)} but allows for more than one default value.
111    *
112    * @param key The property key.
113    * @param type The type to convert the property to.
114    * @param def
115    *    The default values if the session property does not exist or is <jk>null</jk>.
116    *    The first non-null value is returned.
117    * @return The session property.
118    */
119   @SafeVarargs
120   public final <T> T getProperty(String key, Class<T> type, T...def) {
121      return getProperty(key, type, ObjectUtils.firstNonNull(def));
122   }
123
124   /**
125    * Returns the session class property with the specified name.
126    *
127    * @param key The property name.
128    * @param type The class type of the property.
129    * @param def The default value.
130    * @return The property value, or the default value if it doesn't exist.
131    */
132   @SuppressWarnings("unchecked")
133   public final <T> Class<? extends T> getClassProperty(String key, Class<T> type, Class<? extends T> def) {
134      return getProperty(key, Class.class, def);
135   }
136
137   /**
138    * Returns an instantiation of the specified class property.
139    *
140    * @param key The property name.
141    * @param type The class type of the property.
142    * @param def
143    *    The default instance or class to instantiate if the property doesn't exist.
144    * @return A new property instance.
145    */
146   public <T> T getInstanceProperty(String key, Class<T> type, Object def) {
147      return newInstance(type, getProperty(key), def);
148   }
149
150   /**
151    * Returns the specified property as an array of instantiated objects.
152    *
153    * @param key The property name.
154    * @param type The class type of the property.
155    * @param def The default object to return if the property doesn't exist.
156    * @return A new property instance.
157    */
158   @SuppressWarnings("unchecked")
159   public <T> T[] getInstanceArrayProperty(String key, Class<T> type, T[] def) {
160      Object o = getProperty(key);
161      T[] t = null;
162      if (o == null)
163         t = def;
164      else if (o.getClass().isArray()) {
165         if (o.getClass().getComponentType() == type)
166            t = (T[])o;
167         else {
168            t = (T[])Array.newInstance(type, Array.getLength(o));
169            for (int i = 0; i < Array.getLength(o); i++)
170               t[i] = newInstance(type, Array.get(o, i), null);
171         }
172      } else if (o instanceof Collection) {
173         Collection<?> c = (Collection<?>)o;
174         t = (T[])Array.newInstance(type, c.size());
175         int i = 0;
176         for (Object o2 : c)
177            t[i++] = newInstance(type, o2, null);
178      }
179      if (t != null)
180         return t;
181      throw new ConfigException("Could not instantiate property ''{0}'' as type {1}", key, type);
182   }
183
184   /**
185    * Returns the session properties.
186    *
187    * @return The session properties passed in through the constructor.
188    */
189   protected OMap getProperties() {
190      return properties;
191   }
192
193   /**
194    * Returns the session property keys.
195    *
196    * @return The session property keys passed in through the constructor.
197    */
198   public Set<String> getPropertyKeys() {
199      return properties.keySet();
200   }
201
202   @SuppressWarnings("unchecked")
203   private <T> T newInstance(Class<T> type, Object o, Object def) {
204      T t = null;
205      if (o == null) {
206         if (def == null)
207            return null;
208         t = castOrCreate(type, def);
209      }
210      else if (type.isInstance(o))
211         t = (T)o;
212      else if (o.getClass() == Class.class)
213         t = castOrCreate(type, o);
214      else if (o.getClass() == String.class)
215         t = ClassUtils.fromString(type, o.toString());
216      if (t != null)
217         return t;
218      throw new ConfigException("Could not instantiate type ''{0}'' as type {1}", o, type);
219   }
220
221   /**
222    * Adds an arbitrary object to this session's cache.
223    *
224    * <p>
225    * Can be used to store objects for reuse during a session.
226    *
227    * @param key The key.  Can be any string.
228    * @param val The cached object.
229    */
230   public final void addToCache(String key, Object val) {
231      if (cache == null)
232         cache = new TreeMap<>();
233      cache.put(key, val);
234   }
235
236   /**
237    * Adds arbitrary objects to this session's cache.
238    *
239    * <p>
240    * Can be used to store objects for reuse during a session.
241    *
242    * @param cacheObjects
243    *    The objects to add to this session's cache.
244    *    No-op if <jk>null</jk>.
245    */
246   public final void addToCache(Map<String,Object> cacheObjects) {
247      if (cacheObjects != null) {
248         if (cache == null)
249            cache = new TreeMap<>();
250         cache.putAll(cacheObjects);
251      }
252   }
253
254   /**
255    * Returns an object stored in the session cache.
256    *
257    * @param c The class type of the object.
258    * @param key The session object key.
259    * @return The cached object, or <jk>null</jk> if it doesn't exist.
260    */
261   @SuppressWarnings("unchecked")
262   public final <T> T getFromCache(Class<T> c, String key) {
263      return cache == null ? null : (T)cache.get(key);
264   }
265
266   /**
267    * Logs a warning message.
268    *
269    * @param msg The warning message.
270    * @param args Optional {@link MessageFormat}-style arguments.
271    */
272   public void addWarning(String msg, Object... args) {
273      if (warnings == null)
274         warnings = new LinkedList<>();
275      warnings.add((warnings.size() + 1) + ": " + format(msg, args));
276   }
277
278   /**
279    * Returns <jk>true</jk> if warnings occurred in this session.
280    *
281    * @return <jk>true</jk> if warnings occurred in this session.
282    */
283   public final boolean hasWarnings() {
284      return warnings != null && warnings.size() > 0;
285   }
286
287   /**
288    * Returns the warnings that occurred in this session.
289    *
290    * @return The warnings that occurred in this session, or <jk>null</jk> if no warnings occurred.
291    */
292   public final List<String> getWarnings() {
293      return warnings;
294   }
295
296   /**
297    * Throws a {@link BeanRuntimeException} if any warnings occurred in this session.
298    */
299   public void checkForWarnings() {
300      if (warnings != null && ! warnings.isEmpty())
301         throw new BeanRuntimeException("Warnings occurred in session: \n" + join(getWarnings(), "\n"));
302   }
303
304   //-----------------------------------------------------------------------------------------------------------------
305   // Configuration properties
306   //-----------------------------------------------------------------------------------------------------------------
307
308   /**
309    * Configuration property:  Debug mode.
310    *
311    * @see BeanContext#CONTEXT_debug
312    * @return
313    *    <jk>true</jk> if debug mode is enabled.
314    */
315   public boolean isDebug() {
316      return debug;
317   }
318
319   /**
320    * Configuration property:  Locale.
321    *
322    * <p>
323    * The locale is determined in the following order:
324    * <ol>
325    *    <li><c>locale</c> parameter passed in through constructor.
326    *    <li>{@link Context#CONTEXT_locale} entry in parameter passed in through constructor.
327    *    <li>{@link Context#CONTEXT_locale} setting on bean context.
328    *    <li>Locale returned by {@link Locale#getDefault()}.
329    * </ol>
330    *
331    * @see Context#CONTEXT_locale
332    * @return The session locale.
333    */
334   public Locale getLocale() {
335      return locale;
336   }
337
338   /**
339    * Configuration property:  Media type.
340    *
341    * <p>
342    * For example, <js>"application/json"</js>.
343    *
344    * @see Context#CONTEXT_mediaType
345    * @return The media type for this session, or <jk>null</jk> if not specified.
346    */
347   public final MediaType getMediaType() {
348      return mediaType;
349   }
350
351   /**
352    * Configuration property:  Time zone.
353    *
354    * <p>
355    * The timezone is determined in the following order:
356    * <ol>
357    *    <li><c>timeZone</c> parameter passed in through constructor.
358    *    <li>{@link Context#CONTEXT_timeZone} entry in parameter passed in through constructor.
359    *    <li>{@link Context#CONTEXT_timeZone} setting on bean context.
360    * </ol>
361    *
362    * @see Context#CONTEXT_timeZone
363    * @return The session timezone, or <jk>null</jk> if timezone not specified.
364    */
365   public final TimeZone getTimeZone() {
366      return timeZone;
367   }
368
369   /**
370    * Configuration property:  Time zone.
371    *
372    * <p>
373    * The timezone is determined in the following order:
374    * <ol>
375    *    <li><c>timeZone</c> parameter passed in through constructor.
376    *    <li>{@link Context#CONTEXT_timeZone} entry in parameter passed in through constructor.
377    *    <li>{@link Context#CONTEXT_timeZone} setting on bean context.
378    * </ol>
379    *
380    * @see Context#CONTEXT_timeZone
381    * @return The session timezone, or the system timezone if not specified.  Never <jk>null</jk>.
382    */
383   public final ZoneId getTimeZoneId() {
384      return timeZone == null ? ZoneId.systemDefault() : timeZone.toZoneId();
385   }
386
387   //-----------------------------------------------------------------------------------------------------------------
388   // Other methods
389   //-----------------------------------------------------------------------------------------------------------------
390
391   /**
392    * Returns the properties defined on this bean context as a simple map for debugging purposes.
393    *
394    * @return A new map containing the properties defined on this context.
395    */
396   public OMap toMap() {
397      return new DefaultFilteringOMap();
398   }
399
400   @Override /* Object */
401   public String toString() {
402      return SimpleJsonSerializer.DEFAULT_READABLE.toString(toMap());
403   }
404}