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.commons.chain.web;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.HashSet;
022import java.util.Map;
023import java.util.Set;
024
025/**
026 * Implementation of {@code Map} for session attributes with a
027 * parameter-provider.
028 *
029 * @param <S> the type of the session-class
030 * @param <R> the type of the request-class
031 *
032 * @author Graff Stefan
033 * @since Chain 1.3
034 */
035public abstract class AbstractSessionScopeMap<S, R> implements Map<String, Object> {
036
037    /**
038     * The Session-class.
039     */
040    private S session = null;
041
042    /**
043     * The Request-class.
044     */
045    private R request;
046
047    /**
048     * Mutable-Parameter-Map with the session-attributes.
049     */
050    private MutableParameterMap<S, Object> parameterMap = null;
051
052    /**
053     * The constructor for the session attributes.
054     *
055     * @param request the request-class
056     */
057    public AbstractSessionScopeMap(final R request) {
058        this.request = request;
059        sessionExists();
060    }
061
062    /**
063     * Removes all of the mappings from this session-map.
064     * The session-map will be empty after this call returns.
065     */
066    @Override
067    public void clear() {
068        if (sessionExists()) {
069            parameterMap.clear();
070        }
071    }
072
073    /**
074     * Returns {@code true} if this session-map contains a mapping
075     * for the specified key.
076     *
077     * @param key The key whose presence in this session-map is to
078     *            be tested
079     *
080     * @return {@code true} if this session-map contains a mapping
081     *         for the specified key.
082     */
083    @Override
084    public boolean containsKey(Object key) {
085        return sessionExists() && parameterMap.containsKey(key);
086    }
087
088    /**
089     * Returns {@code true} if this session-map maps one or more keys
090     * to the specified value.
091     *
092     * @param value value whose presence in this session-map is to be
093     *        tested
094     *
095     * @return {@code true} if this session-map maps one or more keys
096     *         to the specified value
097     */
098    @Override
099    public boolean containsValue(Object value) {
100        return value != null && sessionExists() && parameterMap.containsValue(value);
101    }
102
103    /**
104     * Returns a {@link Set} view of the mappings contained in this
105     * session-map. The set is not backed by the session-map, so
106     * changes to the session-map are not reflected in the set,
107     * and vice-versa.
108     *
109     * @return a set view of the mappings contained in this
110     *         session-map
111     */
112    @Override
113    public Set<Map.Entry<String, Object>> entrySet() {
114        return sessionExists() ? parameterMap.entrySet() : new HashSet<>();
115    }
116
117    /**
118     * Returns the value to which the specified key is mapped,
119     * or {@code null} if this session-map contains no mapping
120     * for the key.
121     *
122     * <p>A return value of {@code null} does not <i>necessarily</i>
123     * indicate that the session-map contains no mapping for the key;
124     * it's also possible that the session-map explicitly maps the
125     * key to {@code null}. The {@link #containsKey containsKey}
126     * operation may be used to distinguish these two cases.</p>
127     *
128     * @param key the key whose associated value is to be returned
129     *
130     * @return the value to which the specified key is mapped, or
131     *         {@code null} if this session-map contains no mapping for
132     *         the key
133     *
134     * @see #put(Object, Object)
135     */
136    @Override
137    public Object get(Object key) {
138        return sessionExists() ? parameterMap.get(key) : null;
139    }
140
141    /**
142     * Returns {@code true} if this session-map contains no
143     * key-value mappings.
144     *
145     * @return {@code true} if this session-map contains no
146     *         key-value mappings
147     */
148    @Override
149    public boolean isEmpty() {
150        return !sessionExists() || parameterMap.isEmpty();
151    }
152
153    /**
154     * Returns a {@link Set} view of the keys contained in this
155     * session-map. The set is not backed by the session-map, so
156     * changes to the session-map are not reflected in the set, and
157     * vice-versa.
158     *
159     * @return a set view of the keys contained in this session-map
160     */
161    @Override
162    public Set<String> keySet() {
163        return sessionExists() ? parameterMap.keySet() : new HashSet<>();
164    }
165
166    /**
167     * Associates the specified value with the specified key in this
168     * session-map. If the session-map previously contained a
169     * mapping for the key, the old value is replaced.
170     *
171     * @param key key with which the specified value is to be associated
172     * @param value value to be associated with the specified key
173     *
174     * @return the previous value associated with {@code key}, or
175     *         {@code null} if there was no mapping for {@code key}.
176     *         (A {@code null} return can also indicate that the
177     *         session-map previously associated {@code null} with
178     *         {@code key}.)
179     */
180    @Override
181    public Object put(String key, Object value) {
182        if (value == null) {
183            return remove(key);
184        }
185
186        // Ensure the Session is created, if it
187        // doesn't exist
188        return sessionExists(true) ? parameterMap.put(key, value) : null;
189    }
190
191    /**
192     * Copies all of the mappings from the specified map to this
193     * session-map. These mappings will replace any mappings that
194     * this session-map had for any of the keys currently in the
195     * specified map.
196     *
197     * @param map mappings to be stored in this session-map
198     *
199     * @throws NullPointerException if the specified map is null
200     */
201    @Override
202    public void putAll(Map<? extends String, ? extends Object> map) {
203        map.forEach(this::put);
204    }
205
206    /**
207     * Removes the mapping for the specified key from this
208     * session-map if present.
209     *
210     * @param key key whose mapping is to be removed from the
211     *        session-map
212     *
213     * @return the previous value associated with {@code key}, or
214     *         {@code null} if there was no mapping for {@code key}.
215     *         (A {@code null} return can also indicate that the
216     *         session-map previously associated {@code null} with
217     *         {@code key}.)
218     */
219    @Override
220    public Object remove(Object key) {
221        return sessionExists() ? parameterMap.remove(key) : null;
222    }
223
224    /**
225     * Returns the number of key-value mappings in this session-map.
226     *
227     * @return the number of key-value mappings in this session-map
228     */
229    @Override
230    public int size() {
231        return sessionExists() ? parameterMap.size() : 0;
232    }
233
234    /**
235     * Returns a {@link Collection} view of the values contained in
236     * this session-map. The collection is not backed by the
237     * session-map, so changes to the session-map are not
238     * reflected in the collection, and vice-versa.
239     *
240     * @return a view of the values contained in this session-map
241     */
242    @Override
243    public Collection<Object> values() {
244        return sessionExists() ? parameterMap.values() : new ArrayList<>();
245    }
246
247    /**
248     * Returns the hash code value for this session-map. The
249     * hash code of a session-map is defined to be the sum of
250     * the hash codes of each entry in the session-map's
251     * {@code entrySet()} view. This ensures that {@code m1.equals(m2)}
252     * implies that {@code m1.hashCode()==m2.hashCode()} for any two
253     * session-maps {@code m1} and {@code m2}, as required by the
254     * general contract of {@link Object#hashCode}.
255     *
256     * @return the hash code value for this session-map
257     */
258    @Override
259    public int hashCode() {
260        return sessionExists() ? parameterMap.hashCode() : 0;
261    }
262
263    /**
264     * Compares the specified object with this session-map for equality.
265     * Returns {@code true} if the given object is also a session-map
266     * and the two session-maps represent the same mappings. More formally,
267     * two session-maps {@code m1} and {@code m2} represent the same
268     * mappings if {@code m1.entrySet().equals(m2.entrySet())}.
269     *
270     * @param obj object to be compared for equality with this
271     *        session-map
272     *
273     * @return {@code true} if the specified object is equal to this
274     *         session-map
275     */
276    @Override
277    public boolean equals(Object obj) {
278        return sessionExists() && parameterMap.equals(obj);
279    }
280
281    /**
282     * Returns a string representation of this session-map.
283     *
284     * @return a string representation of this session-map
285     */
286    public String toString() {
287        if (request != null) {
288            return "{request: " + request + '}';
289        }
290
291        if (session != null) {
292            final StringBuilder sb = new StringBuilder();
293            sb
294                .append("{session: ")
295                .append(session);
296
297            if (parameterMap != null) {
298                sb
299                    .append(", parameters: ")
300                    .append(parameterMap);
301            }
302            return sb.append('}').toString();
303        }
304        return "{}";
305    }
306
307    /**
308     * Returns the session-class.
309     *
310     * @return the session-class
311     */
312    protected S getSession() {
313        return session;
314    }
315
316    /**
317     * Returns the request-class.
318     *
319     * @return the request-class
320     */
321    protected R getRequest() {
322        return request;
323    }
324
325    /**
326     * Returns {@code true} if a session exists.
327     *
328     * @return {@code true} if a session exists
329     */
330    protected boolean sessionExists() {
331        return sessionExists(false);
332    }
333
334    /**
335     * Returns {@code true} if a session exists or creates a new
336     * session if the parameter {@code create} is set to {@code true}.
337     *
338     * @param create {@code true} to create a new session if no
339     *               session exists
340     *
341     * @return {@code true} if a session exists
342     */
343    protected boolean sessionExists(boolean create) {
344        if (session == null && request != null) {
345            session = getSession(create);
346            if (session != null) {
347                request = null;
348                parameterMap = createParameterMap();
349            }
350        }
351
352        if (session == null) {
353            parameterMap = null;
354            return false;
355        } else {
356            return parameterMap != null;
357        }
358    }
359
360    /**
361     * Returns the current session or, if there is no current session and
362     * the given flag is {@code true}, creates one and returns the new session.
363     *
364     * <p>If the given flag is {@code false} and there is no current
365     * session, this method returns {@code null}.</p>
366     *
367     * @param create {@code true} to create a new session, {@code false} to return
368     *               {@code null} if there is no current session
369     *
370     * @return the session
371     */
372    protected abstract S getSession(boolean create);
373
374    /**
375     * Creates a new mutable-parameter-map to access the session-attributes.
376     *
377     * @return a new mutable-parameter-map to access the session-attributes
378     */
379    protected abstract MutableParameterMap<S, Object> createParameterMap();
380}