1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.opensaml.xml.util;
18
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.Map;
22 import java.util.Set;
23
24 import javax.xml.namespace.QName;
25
26 import net.jcip.annotations.NotThreadSafe;
27
28 import org.opensaml.xml.Configuration;
29 import org.opensaml.xml.NamespaceManager;
30 import org.opensaml.xml.XMLObject;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34
35
36
37
38
39
40 @NotThreadSafe
41 public class AttributeMap implements Map<QName, String> {
42
43
44 private final Logger log = LoggerFactory.getLogger(AttributeMap.class);
45
46
47 private XMLObject attributeOwner;
48
49
50 private Map<QName, String> attributes;
51
52
53
54 private Set<QName> idAttribNames;
55
56
57
58 private Set<QName> qnameAttribNames;
59
60
61
62 private boolean inferQNameValues;
63
64
65
66
67
68
69
70
71 public AttributeMap(XMLObject newOwner) throws NullPointerException {
72 if (newOwner == null) {
73 throw new NullPointerException("Attribute owner XMLObject may not be null");
74 }
75
76 attributeOwner = newOwner;
77 attributes = new LazyMap<QName, String>();
78 idAttribNames = new LazySet<QName>();
79 qnameAttribNames = new LazySet<QName>();
80 }
81
82
83 public String put(QName attributeName, String value) {
84 String oldValue = get(attributeName);
85 if (value != oldValue) {
86 releaseDOM();
87 attributes.put(attributeName, value);
88 if (isIDAttribute(attributeName) || Configuration.isIDAttribute(attributeName)) {
89 attributeOwner.getIDIndex().deregisterIDMapping(oldValue);
90 attributeOwner.getIDIndex().registerIDMapping(value, attributeOwner);
91 }
92 if (!DatatypeHelper.isEmpty(attributeName.getNamespaceURI())) {
93 if (value == null) {
94 attributeOwner.getNamespaceManager().deregisterAttributeName(attributeName);
95 } else {
96 attributeOwner.getNamespaceManager().registerAttributeName(attributeName);
97 }
98 }
99 checkAndDeregisterQNameValue(attributeName, oldValue);
100 checkAndRegisterQNameValue(attributeName, value);
101 }
102
103 return oldValue;
104 }
105
106
107
108
109
110
111
112
113
114
115 public QName put(QName attributeName, QName value) {
116 String oldValueString = get(attributeName);
117
118 QName oldValue = null;
119 if (!DatatypeHelper.isEmpty(oldValueString)) {
120 oldValue = resolveQName(oldValueString, true);
121 }
122
123 if (!DatatypeHelper.safeEquals(oldValue, value)) {
124 releaseDOM();
125 if (value != null) {
126
127 String newStringValue = constructAttributeValue(value);
128 attributes.put(attributeName, newStringValue);
129 registerQNameValue(attributeName, value);
130 attributeOwner.getNamespaceManager().registerAttributeName(attributeName);
131 } else {
132
133 deregisterQNameValue(attributeName);
134 attributeOwner.getNamespaceManager().deregisterAttributeName(attributeName);
135 }
136 }
137
138 return oldValue;
139 }
140
141
142 public void clear() {
143 LazySet<QName> keys = new LazySet<QName>();
144 keys.addAll(attributes.keySet());
145 for (QName attributeName : keys) {
146 remove(attributeName);
147 }
148 }
149
150
151
152
153
154
155 public Set<QName> keySet() {
156 return Collections.unmodifiableSet(attributes.keySet());
157 }
158
159
160 public int size() {
161 return attributes.size();
162 }
163
164
165 public boolean isEmpty() {
166 return attributes.isEmpty();
167 }
168
169
170 public boolean containsKey(Object key) {
171 return attributes.containsKey(key);
172 }
173
174
175 public boolean containsValue(Object value) {
176 return attributes.containsValue(value);
177 }
178
179
180 public String get(Object key) {
181 return attributes.get(key);
182 }
183
184
185 public String remove(Object key) {
186 String removedValue = attributes.remove(key);
187 if (removedValue != null) {
188 releaseDOM();
189 QName attributeName = (QName) key;
190 if (isIDAttribute(attributeName) || Configuration.isIDAttribute(attributeName)) {
191 attributeOwner.getIDIndex().deregisterIDMapping(removedValue);
192 }
193 attributeOwner.getNamespaceManager().deregisterAttributeName(attributeName);
194 checkAndDeregisterQNameValue(attributeName, removedValue);
195 }
196
197 return removedValue;
198 }
199
200
201 public void putAll(Map<? extends QName, ? extends String> t) {
202 if (t != null && t.size() > 0) {
203 for (Entry<? extends QName, ? extends String> entry : t.entrySet()) {
204 put(entry.getKey(), entry.getValue());
205 }
206 }
207 }
208
209
210
211
212
213
214 public Collection<String> values() {
215 return Collections.unmodifiableCollection(attributes.values());
216 }
217
218
219
220
221
222
223 public Set<Entry<QName, String>> entrySet() {
224 return Collections.unmodifiableSet(attributes.entrySet());
225 }
226
227
228
229
230
231
232 public void registerID(QName attributeName) {
233 if (! idAttribNames.contains(attributeName)) {
234 idAttribNames.add(attributeName);
235 }
236
237
238
239 if (containsKey(attributeName)) {
240 attributeOwner.getIDIndex().registerIDMapping(get(attributeName), attributeOwner);
241 }
242 }
243
244
245
246
247
248
249 public void deregisterID(QName attributeName) {
250 if (idAttribNames.contains(attributeName)) {
251 idAttribNames.remove(attributeName);
252 }
253
254
255
256 if (containsKey(attributeName)) {
257 attributeOwner.getIDIndex().deregisterIDMapping(get(attributeName));
258 }
259 }
260
261
262
263
264
265
266
267
268 public boolean isIDAttribute(QName attributeName) {
269 return idAttribNames.contains(attributeName);
270 }
271
272
273
274
275
276
277 public void registerQNameAttribute(QName attributeName) {
278 qnameAttribNames.add(attributeName);
279 }
280
281
282
283
284
285
286 public void deregisterQNameAttribute(QName attributeName) {
287 qnameAttribNames.remove(attributeName);
288 }
289
290
291
292
293
294
295
296 public boolean isQNameAttribute(QName attributeName) {
297 return qnameAttribNames.contains(attributeName);
298 }
299
300
301
302
303
304
305
306
307 public boolean isInferQNameValues() {
308 return inferQNameValues;
309 }
310
311
312
313
314
315
316
317
318 public void setInferQNameValues(boolean flag) {
319 inferQNameValues = flag;
320 }
321
322
323
324
325 private void releaseDOM() {
326 attributeOwner.releaseDOM();
327 attributeOwner.releaseParentDOM(true);
328 }
329
330
331
332
333
334
335
336
337 private void checkAndRegisterQNameValue(QName attributeName, String attributeValue) {
338 if (attributeValue == null) {
339 return;
340 }
341
342 QName qnameValue = checkQName(attributeName, attributeValue);
343 if (qnameValue != null) {
344 log.trace("Attribute '{}' with value '{}' was evaluated to be QName type",
345 attributeName, attributeValue);
346 registerQNameValue(attributeName, qnameValue);
347 } else {
348 log.trace("Attribute '{}' with value '{}' was not evaluated to be QName type",
349 attributeName, attributeValue);
350 }
351
352 }
353
354
355
356
357
358
359
360 private void registerQNameValue(QName attributeName, QName attributeValue) {
361 if (attributeValue == null) {
362 return;
363 }
364
365 String attributeID = NamespaceManager.generateAttributeID(attributeName);
366 log.trace("Registering QName attribute value '{}' under attibute ID '{}'",
367 attributeValue, attributeID);
368 attributeOwner.getNamespaceManager().registerAttributeValue(attributeID, attributeValue);
369 }
370
371
372
373
374
375
376
377
378 private void checkAndDeregisterQNameValue(QName attributeName, String attributeValue) {
379 if (attributeValue == null) {
380 return;
381 }
382
383 QName qnameValue = checkQName(attributeName, attributeValue);
384 if (qnameValue != null) {
385 log.trace("Attribute '{}' with value '{}' was evaluated to be QName type",
386 attributeName, attributeValue);
387 deregisterQNameValue(attributeName);
388 } else {
389 log.trace("Attribute '{}' with value '{}' was not evaluated to be QName type",
390 attributeName, attributeValue);
391 }
392 }
393
394
395
396
397
398
399 private void deregisterQNameValue(QName attributeName) {
400 String attributeID = NamespaceManager.generateAttributeID(attributeName);
401 log.trace("Deregistering QName attribute with attibute ID '{}'", attributeID);
402 attributeOwner.getNamespaceManager().deregisterAttributeValue(attributeID);
403 }
404
405
406
407
408
409
410
411
412 private QName checkQName(QName attributeName, String attributeValue) {
413 log.trace("Checking whether attribute '{}' with value {} is a QName type", attributeName, attributeValue);
414
415 if (attributeValue == null) {
416 log.trace("Attribute value was null, returning null");
417 return null;
418 }
419
420 if (isQNameAttribute(attributeName)) {
421 log.trace("Configuration indicates attribute with name '{}' is a QName type, resolving value QName",
422 attributeName);
423
424 QName valueName = resolveQName(attributeValue, true);
425 if (valueName != null) {
426 log.trace("Successfully resolved attribute value to QName: {}", valueName);
427 } else {
428 log.trace("Could not resolve attribute value to QName, returning null");
429 }
430 return valueName;
431 } else if (isInferQNameValues()) {
432 log.trace("Attempting to infer whether attribute value is a QName");
433
434
435 QName valueName = resolveQName(attributeValue, false);
436 if (valueName != null) {
437 log.trace("Resolved attribute as a QName: '{}'", valueName);
438 } else {
439 log.trace("Attribute value was not resolveable to a QName, returning null");
440 }
441 return valueName;
442 } else {
443 log.trace("Attribute was not registered in configuration as a QName type and QName inference is disabled");
444 return null;
445 }
446
447 }
448
449
450
451
452
453
454
455
456
457
458 private QName resolveQName(String attributeValue, boolean isDefaultNSOK) {
459 if (attributeValue == null) {
460 return null;
461 }
462 log.trace("Attemtping to resolve QName from attribute value '{}'", attributeValue);
463
464
465
466 String candidatePrefix = null;
467 String localPart = null;
468 int ci = attributeValue.indexOf(':');
469 if (ci > -1) {
470 candidatePrefix = attributeValue.substring(0, ci);
471 log.trace("Evaluating candiate namespace prefix '{}'", candidatePrefix);
472 localPart = attributeValue.substring(ci+1);
473 } else {
474
475 if (isDefaultNSOK) {
476 candidatePrefix = null;
477 log.trace("Value did not contain a colon, evaluating as default namespace");
478 localPart = attributeValue;
479 } else {
480 log.trace("Value did not contain a colon, default namespace is disallowed, returning null");
481 return null;
482 }
483 }
484
485 log.trace("Evaluated QName local part as '{}'", localPart);
486
487 String nsURI = XMLObjectHelper.lookupNamespaceURI(attributeOwner, candidatePrefix);
488 log.trace("Resolved namespace URI '{}'", nsURI);
489 if (nsURI != null) {
490 QName name = XMLHelper.constructQName(nsURI, localPart, candidatePrefix);
491 log.trace("Resolved QName '{}'", name);
492 return name;
493 } else {
494 log.trace("Namespace URI for candidate prefix '{}' could not be resolved", candidatePrefix);
495 }
496
497 log.trace("Value was either not a QName, or namespace URI could not be resolved");
498
499 return null;
500 }
501
502
503
504
505
506
507
508 private String constructAttributeValue(QName attributeValue) {
509 String trimmedLocalName = DatatypeHelper.safeTrimOrNullString(attributeValue.getLocalPart());
510
511 if (trimmedLocalName == null) {
512 throw new IllegalArgumentException("Local name may not be null or empty");
513 }
514
515 String qualifiedName;
516 String trimmedPrefix = DatatypeHelper.safeTrimOrNullString(attributeValue.getPrefix());
517 if (trimmedPrefix != null) {
518 qualifiedName = trimmedPrefix + ":" + DatatypeHelper.safeTrimOrNullString(trimmedLocalName);
519 } else {
520 qualifiedName = DatatypeHelper.safeTrimOrNullString(trimmedLocalName);
521 }
522 return qualifiedName;
523 }
524
525 }