001/*
002 * Copyright 2006-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.batch.item;
018
019import java.io.Serializable;
020import java.util.Map;
021import java.util.Map.Entry;
022import java.util.Set;
023import java.util.concurrent.ConcurrentHashMap;
024
025import org.springframework.lang.Nullable;
026
027/**
028 * Object representing a context for an {@link ItemStream}. It is a thin wrapper
029 * for a map that allows optionally for type safety on reads. It also allows for
030 * dirty checking by setting a 'dirty' flag whenever any put is called.
031 *
032 * Note that putting <code>null</code> value is equivalent to removing the entry
033 * for the given key.
034 *
035 * @author Lucas Ward
036 * @author Douglas Kaminsky
037 * @author Mahmoud Ben Hassine
038 */
039@SuppressWarnings("serial")
040public class ExecutionContext implements Serializable {
041
042        private volatile boolean dirty = false;
043
044        private final Map<String, Object> map;
045
046        /**
047         * Default constructor. Initializes a new execution context with an empty
048         * internal map.
049         */
050        public ExecutionContext() {
051                this.map = new ConcurrentHashMap<>();
052        }
053
054        /**
055         * Initializes a new execution context with the contents of another map.
056         *
057         * @param map Initial contents of context.
058         */
059        public ExecutionContext(Map<String, Object> map) {
060                this.map = new ConcurrentHashMap<>(map);
061        }
062
063        /**
064         * Initializes a new {@link ExecutionContext} with the contents of another
065         * {@code ExecutionContext}.
066         *
067         * @param executionContext containing the entries to be copied to this current context.
068         */
069        public ExecutionContext(ExecutionContext executionContext) {
070                this();
071                if (executionContext == null) {
072                        return;
073                }
074                for (Entry<String, Object> entry : executionContext.entrySet()) {
075                        this.map.put(entry.getKey(), entry.getValue());
076                }
077        }
078
079        /**
080         * Adds a String value to the context. Putting <code>null</code>
081         * value for a given key removes the key.
082         *
083         * @param key Key to add to context
084         * @param value Value to associate with key
085         */
086
087        public void putString(String key, @Nullable String value) {
088
089                put(key, value);
090        }
091
092        /**
093         * Adds a Long value to the context.
094         *
095         * @param key Key to add to context
096         * @param value Value to associate with key
097         */
098        public void putLong(String key, long value) {
099
100                put(key, value);
101        }
102
103        /**
104         * Adds an Integer value to the context.
105         *
106         * @param key Key to add to context
107         * @param value Value to associate with key
108         */
109        public void putInt(String key, int value) {
110                put(key, value);
111        }
112
113        /**
114         * Add a Double value to the context.
115         *
116         * @param key Key to add to context
117         * @param value Value to associate with key
118         */
119        public void putDouble(String key, double value) {
120
121                put(key, value);
122        }
123
124        /**
125         * Add an Object value to the context. Putting <code>null</code>
126         * value for a given key removes the key.
127         *
128         * @param key Key to add to context
129         * @param value Value to associate with key
130         */
131        public void put(String key, @Nullable Object value) {
132                if (value != null) {
133                        Object result = this.map.put(key, value);
134                        this.dirty = result==null || result!=null && !result.equals(value);
135                }
136                else {
137                        Object result = this.map.remove(key);
138                        this.dirty = result!=null;
139                }
140        }
141
142        /**
143         * Indicates if context has been changed with a "put" operation since the
144         * dirty flag was last cleared. Note that the last time the flag was cleared
145         * might correspond to creation of the context.
146         *
147         * @return True if "put" operation has occurred since flag was last cleared
148         */
149        public boolean isDirty() {
150                return this.dirty;
151        }
152
153        /**
154         * Typesafe Getter for the String represented by the provided key.
155         *
156         * @param key The key to get a value for
157         * @return The <code>String</code> value
158         */
159        public String getString(String key) {
160
161                return (String) readAndValidate(key, String.class);
162        }
163
164        /**
165         * Typesafe Getter for the String represented by the provided key with
166         * default value to return if key is not represented.
167         *
168         * @param key The key to get a value for
169         * @param defaultString Default to return if key is not represented
170         * @return The <code>String</code> value if key is represented, specified
171         * default otherwise
172         */
173        public String getString(String key, String defaultString) {
174                if (!containsKey(key)) {
175                        return defaultString;
176                }
177
178                return getString(key);
179        }
180
181        /**
182         * Typesafe Getter for the Long represented by the provided key.
183         *
184         * @param key The key to get a value for
185         * @return The <code>Long</code> value
186         */
187        public long getLong(String key) {
188
189                return (Long) readAndValidate(key, Long.class);
190        }
191
192        /**
193         * Typesafe Getter for the Long represented by the provided key with default
194         * value to return if key is not represented.
195         *
196         * @param key The key to get a value for
197         * @param defaultLong Default to return if key is not represented
198         * @return The <code>long</code> value if key is represented, specified
199         * default otherwise
200         */
201        public long getLong(String key, long defaultLong) {
202                if (!containsKey(key)) {
203                        return defaultLong;
204                }
205
206                return getLong(key);
207        }
208
209        /**
210         * Typesafe Getter for the Integer represented by the provided key.
211         *
212         * @param key The key to get a value for
213         * @return The <code>Integer</code> value
214         */
215        public int getInt(String key) {
216
217                return (Integer) readAndValidate(key, Integer.class);
218        }
219
220        /**
221         * Typesafe Getter for the Integer represented by the provided key with
222         * default value to return if key is not represented.
223         *
224         * @param key The key to get a value for
225         * @param defaultInt Default to return if key is not represented
226         * @return The <code>int</code> value if key is represented, specified
227         * default otherwise
228         */
229        public int getInt(String key, int defaultInt) {
230                if (!containsKey(key)) {
231                        return defaultInt;
232                }
233
234                return getInt(key);
235        }
236
237        /**
238         * Typesafe Getter for the Double represented by the provided key.
239         *
240         * @param key The key to get a value for
241         * @return The <code>Double</code> value
242         */
243        public double getDouble(String key) {
244                return (Double) readAndValidate(key, Double.class);
245        }
246
247        /**
248         * Typesafe Getter for the Double represented by the provided key with
249         * default value to return if key is not represented.
250         *
251         * @param key The key to get a value for
252         * @param defaultDouble Default to return if key is not represented
253         * @return The <code>double</code> value if key is represented, specified
254         * default otherwise
255         */
256        public double getDouble(String key, double defaultDouble) {
257                if (!containsKey(key)) {
258                        return defaultDouble;
259                }
260
261                return getDouble(key);
262        }
263
264        /**
265         * Getter for the value represented by the provided key.
266         *
267         * @param key The key to get a value for
268         * @return The value represented by the given key or {@code null} if the key
269         * is not present
270         */
271        @Nullable
272        public Object get(String key) {
273                return this.map.get(key);
274        }
275
276        /**
277         * Utility method that attempts to take a value represented by a given key
278         * and validate it as a member of the specified type.
279         *
280         * @param key The key to validate a value for
281         * @param type Class against which value should be validated
282         * @return Value typed to the specified <code>Class</code>
283         */
284        private Object readAndValidate(String key, Class<?> type) {
285
286                Object value = get(key);
287
288                if (!type.isInstance(value)) {
289                        throw new ClassCastException("Value for key=[" + key + "] is not of type: [" + type + "], it is ["
290                                        + (value == null ? null : "(" + value.getClass() + ")" + value) + "]");
291                }
292
293                return value;
294        }
295
296        /**
297         * Indicates whether or not the context is empty.
298         *
299         * @return True if the context has no entries, false otherwise.
300         * @see java.util.Map#isEmpty()
301         */
302        public boolean isEmpty() {
303                return this.map.isEmpty();
304        }
305
306        /**
307         * Clears the dirty flag.
308         */
309        public void clearDirtyFlag() {
310                this.dirty = false;
311        }
312
313        /**
314         * Returns the entry set containing the contents of this context.
315         *
316         * @return A set representing the contents of the context
317         * @see java.util.Map#entrySet()
318         */
319        public Set<Entry<String, Object>> entrySet() {
320                return this.map.entrySet();
321        }
322
323        /**
324         * Indicates whether or not a key is represented in this context.
325         *
326         * @param key Key to check existence for
327         * @return True if key is represented in context, false otherwise
328         * @see java.util.Map#containsKey(Object)
329         */
330        public boolean containsKey(String key) {
331                return this.map.containsKey(key);
332        }
333
334        /**
335         * Removes the mapping for a key from this context if it is present.
336         *
337         * @param key {@link String} that identifies the entry to be removed from the context.
338         * @return the value that was removed from the context.
339         *
340         * @see java.util.Map#remove(Object)
341         */
342        @Nullable
343        public Object remove(String key) {
344                return this.map.remove(key);
345        }
346
347        /**
348         * Indicates whether or not a value is represented in this context.
349         *
350         * @param value Value to check existence for
351         * @return True if value is represented in context, false otherwise
352         * @see java.util.Map#containsValue(Object)
353         */
354        public boolean containsValue(Object value) {
355                return this.map.containsValue(value);
356        }
357
358        /*
359         * (non-Javadoc)
360         *
361         * @see java.lang.Object#equals(java.lang.Object)
362         */
363        @Override
364        public boolean equals(Object obj) {
365                if (obj instanceof ExecutionContext == false) {
366                        return false;
367                }
368                if (this == obj) {
369                        return true;
370                }
371                ExecutionContext rhs = (ExecutionContext) obj;
372                return this.entrySet().equals(rhs.entrySet());
373        }
374
375        /*
376         * (non-Javadoc)
377         *
378         * @see java.lang.Object#hashCode()
379         */
380        @Override
381        public int hashCode() {
382                return this.map.hashCode();
383        }
384
385        /*
386         * (non-Javadoc)
387         *
388         * @see java.lang.Object#toString()
389         */
390        @Override
391        public String toString() {
392                return this.map.toString();
393        }
394
395        /**
396         * Returns number of entries in the context
397         *
398         * @return Number of entries in the context
399         * @see java.util.Map#size()
400         */
401        public int size() {
402                return this.map.size();
403        }
404
405}