1   package com.trendmicro.grid.acl.metadata;
2   
3   import net.sf.tinyjee.collections.Filter;
4   import net.sf.tinyjee.collections.FilteredCollection;
5   import net.sf.tinyjee.collections.FilteredSet;
6   
7   import java.util.Collection;
8   import java.util.LinkedHashMap;
9   import java.util.Map;
10  import java.util.Set;
11  
12  /**
13   * Implements a Map that transparently validates Meta elements.
14   * <p/>
15   * <ul>Known Issues:
16   * <li>The collections, keySet(), values() and entrySet() do not properly protect the
17   * Meta elements from getting changed.
18   * <li>The Meta elements are not immutable and their value may get changed after adding
19   * them to this map without being checked.
20   * </ul>
21   * In order to resolve some of these issues call {@link #assertValuesAreValidForWrite()}
22   * before attempting to write the elements contained in this map.
23   *
24   * @author juergen_kellerer, 2010-11-30
25   * @version 1.0
26   */
27  public class ValidatedMetaMap extends LinkedHashMap<String, Meta> implements Cloneable {
28  
29  	private static final long serialVersionUID = 3532083475416802063L;
30  
31  	private static final ValidatorResolver NULL_RESOLVER = new ValidatorResolver() {
32  		@Override
33  		public Validator getValidator() {
34  			return null;
35  		}
36  	};
37  
38  	transient ValidatorResolver validatorResolver;
39  
40  	/**
41  	 * Constructs a new validated map with default values for all entries.
42  	 */
43  	public ValidatedMetaMap() {
44  		setValidatorResolver(ValidationContext.getInstance());
45  	}
46  
47  	/**
48  	 * Constructs a new validated map with the given options.
49  	 *
50  	 * @param initialCapacity   the initial capacity of the map.
51  	 * @param validatorResolver the resolver to use.
52  	 */
53  	public ValidatedMetaMap(int initialCapacity, ValidatorResolver validatorResolver) {
54  		super(initialCapacity);
55  		setValidatorResolver(validatorResolver);
56  	}
57  
58  	public ValidatorResolver getValidatorResolver() {
59  		return validatorResolver;
60  	}
61  
62  	public void setValidatorResolver(ValidatorResolver resolver) {
63  		this.validatorResolver = resolver == null ? NULL_RESOLVER : resolver;
64  	}
65  
66  	/**
67  	 * Returns the used validator for the current context.
68  	 *
69  	 * @return the used validator for the current context.
70  	 */
71  	public Validator getValidator() {
72  		return validatorResolver.getValidator();
73  	}
74  
75  	/**
76  	 * Validates that all contained values are valid and writable in the current context.
77  	 */
78  	public void assertValuesAreValidForWrite() {
79  		final Validator validator = validatorResolver.getValidator();
80  		if (validator != null) {
81  			for (Meta meta : super.values())
82  				assertIsValidAndWritable(validator, meta.getName(), meta);
83  		}
84  	}
85  
86  	private void assertIsValidAndWritable(final Validator validator, final String key, final Meta value) {
87  		if (!validator.isWritable(key)) {
88  			throw new IllegalStateException("Cannot write the meta element named as '" + key +
89  					"'. It is not allowed to set or change the value in the current context.");
90  		}
91  
92  		if (!validator.isValid(value)) {
93  			throw new IllegalArgumentException("Cannot write the meta element '" + value + "'. " +
94  					"The format of the element is invalid with regards to the active meta data profile " +
95  					"within the current context.");
96  		}
97  	}
98  
99  	/**
100 	 * {@inheritDoc}
101 	 */
102 	@Override
103 	public Meta get(Object key) {
104 		final Validator validator = validatorResolver.getValidator();
105 		if (validator == null || validator.isReadable((String) key))
106 			return super.get(key);
107 		return null;
108 	}
109 
110 	/**
111 	 * {@inheritDoc}
112 	 */
113 	@Override
114 	public Meta put(String key, Meta value) {
115 		final Validator validator = validatorResolver.getValidator();
116 		// Note: We may validate only when this value existed already.
117 		//       Metadata cannot be built due to timing issues when validating entries that do not yet exist.
118 		if (validator != null && containsKey(key))
119 			assertIsValidAndWritable(validator, key, value);
120 		return super.put(key, value);
121 	}
122 
123 	/**
124 	 * Puts the given meta entry without validating it.
125 	 *
126 	 * @param value the meta entry to put.
127 	 */
128 	void putWithoutValidation(Meta value) {
129 		super.put(value.getName(), value);
130 	}
131 
132 	/**
133 	 * {@inheritDoc}
134 	 */
135 	@Override
136 	public void putAll(Map<? extends String, ? extends Meta> m) {
137 		final Validator validator = validatorResolver.getValidator();
138 		if (validator != null) {
139 			// Note: We may validate only when this value existed already.
140 			//       Metadata cannot be built due to timing issues when validating entries that do not yet exist.
141 			for (Map.Entry<? extends String, ? extends Meta> entry : m.entrySet())
142 				if (containsKey(entry.getKey()))
143 					assertIsValidAndWritable(validator, entry.getKey(), entry.getValue());
144 		}
145 		super.putAll(m);
146 	}
147 
148 	/**
149 	 * {@inheritDoc}
150 	 */
151 	@Override
152 	public Meta remove(Object key) {
153 		final Validator validator = validatorResolver.getValidator();
154 		if (validator != null && !validator.isWritable((String) key)) {
155 			throw new IllegalStateException("Cannot remove the meta element named as '" + key +
156 					"'. It is not allowed to change the value in the current context.");
157 		}
158 
159 		return super.remove(key);
160 	}
161 
162 	/**
163 	 * {@inheritDoc}
164 	 */
165 	@Override
166 	public Set<String> keySet() {
167 		final Validator validator = validatorResolver.getValidator();
168 		if (validator == null)
169 			return super.keySet();
170 		else {
171 			return new FilteredSet<String>(super.keySet(), new Filter<String>() {
172 				@Override
173 				public boolean accept(String value) {
174 					return validator.isReadable(value);
175 				}
176 			});
177 		}
178 	}
179 
180 	/**
181 	 * {@inheritDoc}
182 	 */
183 	@Override
184 	public Collection<Meta> values() {
185 		final Validator validator = validatorResolver.getValidator();
186 		if (validator == null)
187 			return super.values();
188 		else {
189 			return new FilteredCollection<Meta>(super.values(), new Filter<Meta>() {
190 				@Override
191 				public boolean accept(Meta value) {
192 					return value == null || validator.isReadable(value.getName());
193 				}
194 			});
195 		}
196 	}
197 
198 	/**
199 	 * {@inheritDoc}
200 	 */
201 	@Override
202 	public Set<Map.Entry<String, Meta>> entrySet() {
203 		final Validator validator = validatorResolver.getValidator();
204 		if (validator == null)
205 			return super.entrySet();
206 		else {
207 			return new FilteredSet<Map.Entry<String, Meta>>(super.entrySet(), new Filter<Map.Entry<String, Meta>>() {
208 				@Override
209 				public boolean accept(Map.Entry<String, Meta> value) {
210 					return validator.isReadable(value.getKey());
211 				}
212 			});
213 		}
214 	}
215 
216 	/**
217 	 * Returns all values without validating anything.
218 	 *
219 	 * @return all values without validating anything.
220 	 */
221 	Collection<Meta> valuesWithoutValidation() {
222 		return super.values();
223 	}
224 
225 	/**
226 	 * {@inheritDoc}
227 	 */
228 	@Override
229 	public ValidatedMetaMap clone() {
230 		return (ValidatedMetaMap) super.clone();
231 	}
232 }