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}