37fe0b3463f3b124d36382b0094e2ebbac5161a8
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / aa / attrresolv / AttributeResolver.java
1 /*
2  * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
3  * All rights reserved
4  * 
5  * 
6  * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
7  * following conditions are met:
8  * 
9  * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
10  * disclaimer.
11  * 
12  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
13  * disclaimer in the documentation and/or other materials provided with the distribution, if any, must include the
14  * following acknowledgment: "This product includes software developed by the University Corporation for Advanced
15  * Internet Development <http://www.ucaid.edu> Internet2 Project. Alternately, this acknowledegement may appear in the
16  * software itself, if and wherever such third-party acknowledgments normally appear.
17  * 
18  * Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor the University Corporation for
19  * Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote products derived from this software
20  * without specific prior written permission. For written permission, please contact shibboleth@shibboleth.org
21  * 
22  * Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the University Corporation
23  * for Advanced Internet Development, nor may Shibboleth appear in their name, without prior written permission of the
24  * University Corporation for Advanced Internet Development.
25  * 
26  * 
27  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR
28  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
29  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE,
30  * ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
31  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
32  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
35  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36  */
37
38 package edu.internet2.middleware.shibboleth.aa.attrresolv;
39
40 import java.io.IOException;
41 import java.security.Principal;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.HashMap;
45 import java.util.HashSet;
46 import java.util.Iterator;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Set;
50
51 import javax.naming.directory.Attributes;
52 import javax.naming.directory.BasicAttributes;
53
54 import org.apache.log4j.Logger;
55 import org.apache.xerces.parsers.DOMParser;
56 import org.w3c.dom.Document;
57 import org.w3c.dom.Element;
58 import org.w3c.dom.Node;
59 import org.w3c.dom.NodeList;
60 import org.xml.sax.EntityResolver;
61 import org.xml.sax.ErrorHandler;
62 import org.xml.sax.InputSource;
63 import org.xml.sax.SAXException;
64 import org.xml.sax.SAXParseException;
65
66 import edu.internet2.middleware.shibboleth.aa.AAConfig;
67 import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolverAttributeSet.ResolverAttributeIterator;
68 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.ValueHandler;
69 import edu.internet2.middleware.shibboleth.common.ShibResource;
70 import edu.internet2.middleware.shibboleth.common.ShibResource.ResourceNotAvailableException;
71
72 /**
73  * An engine for obtaining attribute values for specified principals. Attributes values are resolved using a directed
74  * graph of pluggable attribute definitions and data connectors.
75  * 
76  * @author Walter Hoehn (wassa@columbia.edu)
77  *  
78  */
79
80 public class AttributeResolver {
81
82         private static Logger log = Logger.getLogger(AttributeResolver.class.getName());
83         private HashMap plugIns = new HashMap();
84         private ResolverCache resolverCache = new ResolverCache();
85         public static final String resolverNamespace = "urn:mace:shibboleth:resolver:1.0";
86
87         public AttributeResolver(AAConfig configuration) throws AttributeResolverException {
88                 
89                 if (configuration == null || configuration.getResolverConfigLocation() == null) {
90                         log.error("No Attribute Resolver configuration file specified.");
91                         throw new AttributeResolverException("No Attribute Resolver configuration file specified.");
92                 }
93                 
94                 loadConfig(configuration.getResolverConfigLocation());
95         }
96         
97         public AttributeResolver(String configFileLocation) throws AttributeResolverException {
98                 loadConfig(configFileLocation);
99         }
100
101         private void loadConfig(String configFile) throws AttributeResolverException {
102                 try {
103                         ShibResource config = new ShibResource(configFile, this.getClass());
104                         DOMParser parser = new DOMParser();
105                         parser.setFeature("http://xml.org/sax/features/validation", true);
106                         parser.setFeature("http://apache.org/xml/features/validation/schema", true);
107                         parser.setEntityResolver(new EntityResolver() {
108                                 public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
109                                         if (systemId.endsWith("shibboleth-resolver-1.0.xsd")) {
110                                                 try {
111                                                         return new InputSource(
112                                                                 new ShibResource("/schemas/shibboleth-resolver-1.0.xsd", this.getClass())
113                                                                         .getInputStream());
114                                                 } catch (IOException e) {
115                                                         throw new SAXException("Could not load entity: " + e);
116                                                 }
117                                         } else {
118                                                 return null;
119                                         }
120                                 }
121                         });
122
123                         parser.setErrorHandler(new ErrorHandler() {
124                                 public void error(SAXParseException arg0) throws SAXException {
125                                         throw new SAXException("Error parsing xml file: " + arg0);
126                                 }
127                                 public void fatalError(SAXParseException arg0) throws SAXException {
128                                         throw new SAXException("Error parsing xml file: " + arg0);
129                                 }
130                                 public void warning(SAXParseException arg0) throws SAXException {
131                                         throw new SAXException("Error parsing xml file: " + arg0);
132                                 }
133                         });
134                         parser.parse(new InputSource(config.getInputStream()));
135                         loadConfig(parser.getDocument());
136
137                 } catch (ResourceNotAvailableException e) {
138                         log.error("No Attribute Resolver configuration could be loaded from (" + configFile + "): " + e);
139                         throw new AttributeResolverException("No Attribute Resolver configuration found.");
140                 } catch (SAXException e) {
141                         log.error("Error parsing Attribute Resolver Configuration file: " + e);
142                         throw new AttributeResolverException("Error parsing Attribute Resolver Configuration file.");
143                 } catch (IOException e) {
144                         log.error("Error reading Attribute Resolver Configuration file: " + e);
145                         throw new AttributeResolverException("Error reading Attribute Resolver Configuration file.");
146                 }
147         }
148
149         private void loadConfig(Document document) throws AttributeResolverException {
150
151                 log.info("Configuring Attribute Resolver.");
152                 if (!document.getDocumentElement().getTagName().equals("AttributeResolver")) {
153                         log.error("Configuration must include <AttributeResolver> as the root node.");
154                         throw new AttributeResolverException("Cannot load Attribute Resolver.");
155                 }
156
157                 NodeList plugInNodes =
158                         document.getElementsByTagNameNS(resolverNamespace, "AttributeResolver").item(0).getChildNodes();
159                 if (plugInNodes.getLength() <= 0) {
160                         log.error("Configuration inclues no PlugIn definitions.");
161                         throw new AttributeResolverException("Cannot load Attribute Resolver.");
162                 }
163                 for (int i = 0; plugInNodes.getLength() > i; i++) {
164                         if (plugInNodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
165                                 try {
166                                         log.info("Found a PlugIn. Loading...");
167                                         ResolutionPlugIn plugIn = ResolutionPlugInFactory.createPlugIn((Element) plugInNodes.item(i));
168                                         registerPlugIn(plugIn, plugIn.getId());
169                                 } catch (DuplicatePlugInException dpe) {
170                                         log.warn("Skipping PlugIn: " + dpe.getMessage());
171                                 } catch (ClassCastException cce) {
172                                         log.error("Problem realizing PlugIn configuration" + cce.getMessage());
173                                 } catch (AttributeResolverException are) {
174                                         log.warn("Skipping PlugIn: " + ((Element) plugInNodes.item(i)).getAttribute("id"));
175                                 }
176                         }
177                 }
178
179                 verifyPlugIns();
180                 log.info("Configuration complete.");
181         }
182
183         private void verifyPlugIns() throws AttributeResolverException {
184
185                 log.info("Verifying PlugIn graph consitency.");
186                 Set inconsistent = new HashSet();
187                 Iterator registered = plugIns.keySet().iterator();
188
189                 while (registered.hasNext()) {
190                         ResolutionPlugIn plugIn = lookupPlugIn((String) registered.next());
191                         log.debug("Checking PlugIn (" + plugIn.getId() + ") for consistency.");
192                         verifyPlugIn(plugIn, new HashSet(), inconsistent);
193                 }
194
195                 if (!inconsistent.isEmpty()) {
196                         log.info("Unloading inconsistent PlugIns.");
197                         Iterator inconsistentIt = inconsistent.iterator();
198                         while (inconsistentIt.hasNext()) {
199                                 plugIns.remove(inconsistentIt.next());
200                         }
201                 }
202
203                 if (plugIns.size() < 1) {
204                         log.error("Failed to load any PlugIn definitions.");
205                         throw new AttributeResolverException("Cannot load Attribute Resolver.");
206                 }
207
208         }
209
210         private void verifyPlugIn(ResolutionPlugIn plugIn, Set verifyChain, Set inconsistent) {
211
212                 //Short-circuit if we have already found this PlugIn to be inconsistent
213                 if (inconsistent.contains(plugIn.getId())) {
214                         return;
215                 }
216
217                 //Make sure that we don't have a circular dependency
218                 if (verifyChain.contains(plugIn.getId())) {
219                         log.error(
220                                 "The PlugIn (" + plugIn.getId() + ") is inconsistent.  It is involved in a circular dependency chain.");
221                         inconsistent.add(plugIn.getId());
222                         return;
223                 }
224
225                 //Recursively go through all DataConnector dependencies and make sure all are registered and consistent.
226                 List depends = new ArrayList();
227                 depends.addAll(Arrays.asList(plugIn.getDataConnectorDependencyIds()));
228                 Iterator dependsIt = depends.iterator();
229                 while (dependsIt.hasNext()) {
230                         String key = (String) dependsIt.next();
231                         if (!plugIns.containsKey(key)) {
232                                 log.error(
233                                         "The PlugIn ("
234                                                 + plugIn.getId()
235                                                 + ") is inconsistent.  It depends on a PlugIn ("
236                                                 + key
237                                                 + ") that is not registered.");
238                                 inconsistent.add(plugIn.getId());
239                                 return;
240                         }
241
242                         ResolutionPlugIn dependent = lookupPlugIn(key);
243                         if (!(dependent instanceof DataConnectorPlugIn)) {
244                                 log.error(
245                                         "The PlugIn ("
246                                                 + plugIn.getId()
247                                                 + ") is inconsistent.  It depends on a PlugIn ("
248                                                 + key
249                                                 + ") that is mislabeled as an DataConnectorPlugIn.");
250                                 inconsistent.add(plugIn.getId());
251                                 return;
252                         }
253
254                         verifyChain.add(plugIn.getId());
255                         verifyPlugIn(dependent, verifyChain, inconsistent);
256
257                         if (inconsistent.contains(key)) {
258                                 log.error(
259                                         "The PlugIn ("
260                                                 + plugIn.getId()
261                                                 + ") is inconsistent.  It depends on a PlugIn ("
262                                                 + key
263                                                 + ") that is inconsistent.");
264                                 inconsistent.add(plugIn.getId());
265                                 return;
266                         }
267                 }
268                 verifyChain.remove(plugIn.getId());
269
270                 //Recursively go through all AttributeDefinition dependencies and make sure all are registered and consistent.
271                 depends.clear();
272                 depends.addAll(Arrays.asList(plugIn.getAttributeDefinitionDependencyIds()));
273                 dependsIt = depends.iterator();
274                 while (dependsIt.hasNext()) {
275                         String key = (String) dependsIt.next();
276
277                         if (!plugIns.containsKey(key)) {
278                                 log.error(
279                                         "The PlugIn ("
280                                                 + plugIn.getId()
281                                                 + ") is inconsistent.  It depends on a PlugIn ("
282                                                 + key
283                                                 + ") that is not registered.");
284                                 inconsistent.add(plugIn.getId());
285                                 return;
286                         }
287
288                         ResolutionPlugIn dependent = lookupPlugIn(key);
289                         if (!(dependent instanceof AttributeDefinitionPlugIn)) {
290                                 log.error(
291                                         "The PlugIn ("
292                                                 + plugIn.getId()
293                                                 + ") is inconsistent.  It depends on a PlugIn ("
294                                                 + key
295                                                 + ") that is mislabeled as an AttributeDefinitionPlugIn.");
296                                 inconsistent.add(plugIn.getId());
297                                 return;
298                         }
299
300                         verifyChain.add(plugIn.getId());
301                         verifyPlugIn(dependent, verifyChain, inconsistent);
302
303                         if (inconsistent.contains(key)) {
304                                 log.error(
305                                         "The PlugIn ("
306                                                 + plugIn.getId()
307                                                 + ") is inconsistent.  It depends on a PlugIn ("
308                                                 + key
309                                                 + ") that is inconsistent.");
310                                 inconsistent.add(plugIn.getId());
311                                 return;
312                         }
313                 }
314                 verifyChain.remove(plugIn.getId());
315
316                 //Check the failover dependency, if there is one.
317                 if (plugIn instanceof DataConnectorPlugIn) {
318                         String key = ((DataConnectorPlugIn) plugIn).getFailoverDependencyId();
319                         if (key != null) {
320                                 if (!plugIns.containsKey(key)) {
321                                         log.error(
322                                                 "The PlugIn ("
323                                                         + plugIn.getId()
324                                                         + ") is inconsistent.  It depends on a PlugIn ("
325                                                         + key
326                                                         + ") that is not registered.");
327                                         inconsistent.add(plugIn.getId());
328                                         return;
329                                 }
330
331                                 ResolutionPlugIn dependent = lookupPlugIn(key);
332                                 if (!(dependent instanceof DataConnectorPlugIn)) {
333                                         log.error(
334                                                 "The PlugIn ("
335                                                         + plugIn.getId()
336                                                         + ") is inconsistent.  It depends on a fail-over PlugIn ("
337                                                         + key
338                                                         + ") that is not a DataConnectorPlugIn.");
339                                         inconsistent.add(plugIn.getId());
340                                         return;
341                                 }
342
343                                 verifyChain.add(plugIn.getId());
344                                 verifyPlugIn(dependent, verifyChain, inconsistent);
345
346                                 if (inconsistent.contains(key)) {
347                                         log.error(
348                                                 "The PlugIn ("
349                                                         + plugIn.getId()
350                                                         + ") is inconsistent.  It depends on a PlugIn ("
351                                                         + key
352                                                         + ") that is inconsistent.");
353                                         inconsistent.add(plugIn.getId());
354                                         return;
355                                 }
356                         }
357                 }
358                 verifyChain.remove(plugIn.getId());
359         }
360
361         private void registerPlugIn(ResolutionPlugIn connector, String id) throws DuplicatePlugInException {
362
363                 if (plugIns.containsKey(id)) {
364                         log.error("A PlugIn is already registered with the Id (" + id + ").");
365                         throw new DuplicatePlugInException("Found a duplicate PlugIn Id.");
366                 }
367                 plugIns.put(id, connector);
368                 log.info("Registered PlugIn: (" + id + ")");
369
370         }
371
372         private ResolutionPlugIn lookupPlugIn(String id) {
373                 return (ResolutionPlugIn) plugIns.get(id);
374         }
375
376         /**
377          * Resolve a set of attributes for a particular principal and requester.
378          * 
379          * @param principal
380          *            the <code>Principal</code> for which the attributes should be resolved
381          * @param requester
382          *            the name of the requesting entity
383          * @param attributes
384          *            the set of attributes to be resolved
385          */
386         public void resolveAttributes(Principal principal, String requester, ResolverAttributeSet attributes) {
387
388                 HashMap requestCache = new HashMap();
389                 ResolverAttributeIterator iterator = attributes.resolverAttributeIterator();
390
391                 while (iterator.hasNext()) {
392                         ResolverAttribute attribute = iterator.nextResolverAttribute();
393                         try {
394                                 if (lookupPlugIn(attribute.getName()) == null) {
395                                         log.warn("No PlugIn registered for attribute: (" + attribute.getName() + ")");
396                                         iterator.remove();
397                                 } else {
398                                         log.info("Resolving attribute: (" + attribute.getName() + ")");
399                                         if (attribute.resolved()) {
400                                                 log.debug(
401                                                         "Attribute ("
402                                                                 + attribute.getName()
403                                                                 + ") already resolved for this request.  No need for further resolution.");
404
405                                         } else {
406                                                 resolveAttribute(attribute, principal, requester, requestCache, attributes);
407                                         }
408
409                                         if (!attribute.hasValues()) {
410                                                 iterator.remove();
411                                         }
412                                 }
413                         } catch (ResolutionPlugInException rpe) {
414                                 log.error("Problem encountered while resolving attribute: (" + attribute.getName() + "): " + rpe);
415                                 iterator.remove();
416                         }
417                 }
418         }
419
420         public String[] listRegisteredAttributeDefinitionPlugIns() {
421
422                 log.debug("Listing available Attribute Definition PlugIns.");
423                 Set found = new HashSet();
424                 Iterator registered = plugIns.keySet().iterator();
425
426                 while (registered.hasNext()) {
427                         ResolutionPlugIn plugIn = lookupPlugIn((String) registered.next());
428                         if (plugIn instanceof AttributeDefinitionPlugIn) {
429                                 found.add(((AttributeDefinitionPlugIn) plugIn).getId());
430                         }
431                 }
432
433                 if (log.isDebugEnabled()) {
434                         for (Iterator iterator = found.iterator(); iterator.hasNext();) {
435                                 log.debug("Found registered Attribute Definition: " + (String) iterator.next());
436                         }
437                 }
438                 return (String[]) found.toArray(new String[0]);
439         }
440
441         private Attributes resolveConnector(
442                 String connector,
443                 Principal principal,
444                 String requester,
445                 Map requestCache,
446                 ResolverAttributeSet requestedAttributes)
447                 throws ResolutionPlugInException {
448
449                 DataConnectorPlugIn currentDefinition = (DataConnectorPlugIn) lookupPlugIn(connector);
450
451                 //Check to see if we have already resolved the connector during this request
452                 if (requestCache.containsKey(currentDefinition.getId())) {
453                         log.debug(
454                                 "Connector ("
455                                         + currentDefinition.getId()
456                                         + ") already resolved for this request, using cached version");
457                         return (Attributes) requestCache.get(currentDefinition.getId());
458                 }
459
460                 //Check to see if we have a cached resolution for this connector
461                 if (currentDefinition.getTTL() > 0) {
462                         Attributes cachedAttributes = resolverCache.getResolvedConnector(principal, currentDefinition.getId());
463                         if (cachedAttributes != null) {
464                                 log.debug(
465                                         "Connector ("
466                                                 + currentDefinition.getId()
467                                                 + ") resolution cached from a previous request, using cached version");
468                                 return cachedAttributes;
469                         }
470                 }
471
472                 //Resolve all attribute dependencies
473                 String[] attributeDependencies = currentDefinition.getAttributeDefinitionDependencyIds();
474                 Dependencies depends = new Dependencies();
475
476                 for (int i = 0; attributeDependencies.length > i; i++) {
477                         log.debug(
478                                 "Connector ("
479                                         + currentDefinition.getId()
480                                         + ") depends on attribute ("
481                                         + attributeDependencies[i]
482                                         + ").");
483                         ResolverAttribute dependant = requestedAttributes.getByName(attributeDependencies[i]);
484                         if (dependant == null) {
485                                 dependant = new DependentOnlyResolutionAttribute(attributeDependencies[i]);
486                         }
487                         resolveAttribute(dependant, principal, requester, requestCache, requestedAttributes);
488                         depends.addAttributeResolution(attributeDependencies[i], dependant);
489
490                 }
491
492                 //Resolve all connector dependencies
493                 String[] connectorDependencies = currentDefinition.getDataConnectorDependencyIds();
494                 for (int i = 0; connectorDependencies.length > i; i++) {
495                         log.debug(
496                                 "Connector ("
497                                         + currentDefinition.getId()
498                                         + ") depends on connector ("
499                                         + connectorDependencies[i]
500                                         + ").");
501                         depends.addConnectorResolution(
502                                 connectorDependencies[i],
503                                 resolveConnector(connectorDependencies[i], principal, requester, requestCache, requestedAttributes));
504                 }
505
506                 //Resolve the connector
507                 Attributes resolvedAttributes = null;
508                 try {
509                         resolvedAttributes = currentDefinition.resolve(principal, requester, depends);
510
511                         //Add attribute resolution to cache
512                         if (currentDefinition.getTTL() > 0) {
513                                 resolverCache.cacheConnectorResolution(
514                                         principal,
515                                         currentDefinition.getId(),
516                                         currentDefinition.getTTL(),
517                                         resolvedAttributes);
518                         }
519                 } catch (ResolutionPlugInException e) {
520                         // Something went wrong, so check for a fail-over...
521                         if (currentDefinition.getFailoverDependencyId() != null) {
522                                 log.warn("Connector (" + currentDefinition.getId() + ") failed, invoking failover dependency");
523                                 resolvedAttributes =
524                                         resolveConnector(
525                                                 currentDefinition.getFailoverDependencyId(),
526                                                 principal,
527                                                 requester,
528                                                 requestCache,
529                                                 requestedAttributes);
530                         } else if (currentDefinition.getPropagateErrors()) {
531                                 throw e;
532                         } else {
533                                 log.warn(
534                                         "Connector ("
535                                                 + currentDefinition.getId()
536                                                 + ") returning empty attribute set instead of propagating error: "
537                                                 + e);
538                                 resolvedAttributes = new BasicAttributes();
539                         }
540                 }
541
542                 //Cache for this request
543                 requestCache.put(currentDefinition.getId(), resolvedAttributes);
544                 return resolvedAttributes;
545         }
546
547         private void resolveAttribute(
548                 ResolverAttribute attribute,
549                 Principal principal,
550                 String requester,
551                 Map requestCache,
552                 ResolverAttributeSet requestedAttributes)
553                 throws ResolutionPlugInException {
554
555                 AttributeDefinitionPlugIn currentDefinition = (AttributeDefinitionPlugIn) lookupPlugIn(attribute.getName());
556
557                 //Check to see if we have already resolved the attribute during this request
558                 // (this checks dependency-only attributes and attributes resolved with no values
559                 if (requestCache.containsKey(currentDefinition.getId())) {
560                         log.debug(
561                                 "Attribute ("
562                                         + currentDefinition.getId()
563                                         + ") already resolved for this request, using cached version");
564                         attribute.resolveFromCached((ResolverAttribute) requestCache.get(currentDefinition.getId()));
565                         return;
566                 }
567
568                 //Check to see if we have already resolved the attribute during this request
569                 // (this checks attributes that were submitted to the AR for resolution)
570                 ResolverAttribute requestedAttribute = requestedAttributes.getByName(currentDefinition.getId());
571                 if (requestedAttribute != null) {
572                         if (requestedAttribute.resolved()) {
573                                 attribute.resolveFromCached(requestedAttribute);
574                         }
575                 }
576
577                 //Check to see if we have a cached resolution for this attribute
578                 if (currentDefinition.getTTL() > 0) {
579                         ResolverAttribute cachedAttribute =
580                                 resolverCache.getResolvedAttribute(principal, currentDefinition.getId());
581                         if (cachedAttribute != null) {
582                                 log.debug(
583                                         "Attribute ("
584                                                 + currentDefinition.getId()
585                                                 + ") resolution cached from a previous request, using cached version");
586                                 attribute.resolveFromCached(cachedAttribute);
587                                 return;
588                         }
589                 }
590
591                 //Resolve all attribute dependencies
592                 Dependencies depends = new Dependencies();
593                 String[] attributeDependencies = currentDefinition.getAttributeDefinitionDependencyIds();
594
595                 boolean dependancyOnly = false;
596                 for (int i = 0; attributeDependencies.length > i; i++) {
597                         log.debug(
598                                 "Attribute (" + attribute.getName() + ") depends on attribute (" + attributeDependencies[i] + ").");
599                         ResolverAttribute dependant = requestedAttributes.getByName(attributeDependencies[i]);
600                         if (dependant == null) {
601                                 dependancyOnly = true;
602                                 dependant = new DependentOnlyResolutionAttribute(attributeDependencies[i]);
603                         }
604                         resolveAttribute(dependant, principal, requester, requestCache, requestedAttributes);
605                         depends.addAttributeResolution(attributeDependencies[i], dependant);
606
607                 }
608
609                 //Resolve all connector dependencies
610                 String[] connectorDependencies = currentDefinition.getDataConnectorDependencyIds();
611                 for (int i = 0; connectorDependencies.length > i; i++) {
612                         log.debug(
613                                 "Attribute (" + attribute.getName() + ") depends on connector (" + connectorDependencies[i] + ").");
614                         depends.addConnectorResolution(
615                                 connectorDependencies[i],
616                                 resolveConnector(connectorDependencies[i], principal, requester, requestCache, requestedAttributes));
617                 }
618
619                 //Resolve the attribute
620                 try {
621                         currentDefinition.resolve(attribute, principal, requester, depends);
622
623                         //Add attribute resolution to cache
624                         if (currentDefinition.getTTL() > 0) {
625                                 resolverCache.cacheAttributeResolution(
626                                         principal,
627                                         attribute.getName(),
628                                         currentDefinition.getTTL(),
629                                         attribute);
630                         }
631                 } catch (ResolutionPlugInException e) {
632                         if (currentDefinition.getPropagateErrors()) {
633                                 throw e;
634                         } else {
635                                 log.warn(
636                                         "Attribute ("
637                                                 + currentDefinition.getId()
638                                                 + ") returning no values instead of propagating error: "
639                                                 + e);
640                         }
641                 }
642
643                 //If necessary, cache for this request
644                 if (dependancyOnly || !attribute.hasValues()) {
645                         requestCache.put(currentDefinition.getId(), attribute);
646                 }
647         }
648
649         private class DuplicatePlugInException extends Exception {
650                 public DuplicatePlugInException(String message) {
651                         super(message);
652                 }
653         }
654
655         class DependentOnlyResolutionAttribute implements ResolverAttribute {
656                 String name;
657                 ArrayList values = new ArrayList();
658                 boolean resolved = false;
659
660                 DependentOnlyResolutionAttribute(String name) {
661                         this.name = name;
662                 }
663
664                 public String getName() {
665                         return name;
666                 }
667
668                 public boolean resolved() {
669                         return resolved;
670                 }
671
672                 public void setResolved() {
673                         resolved = true;
674                 }
675
676                 public void resolveFromCached(ResolverAttribute attribute) {
677                 }
678
679                 public void setLifetime(long lifetime) {
680                 }
681
682                 public long getLifetime() {
683                         return 0;
684                 }
685
686                 public void addValue(Object value) {
687                         values.add(value);
688                 }
689
690                 public Iterator getValues() {
691                         return values.iterator();
692                 }
693
694                 public boolean hasValues() {
695                         if (values.isEmpty()) {
696                                 return false;
697                         }
698                         return true;
699                 }
700
701                 public void registerValueHandler(ValueHandler handler) {
702                 }
703
704                 public ValueHandler getRegisteredValueHandler() {
705                         return null;
706                 }
707         }
708
709         /**
710          * Cleanup resources that won't be released when this object is garbage-collected
711          */
712         public void destroy() {
713                 resolverCache.destroy();
714         }
715 }