Parse Service Provider configuration files
authorgilbert <gilbert@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Thu, 28 Oct 2004 13:43:09 +0000 (13:43 +0000)
committergilbert <gilbert@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Thu, 28 Oct 2004 13:43:09 +0000 (13:43 +0000)
git-svn-id: https://subversion.switch.ch/svn/shibboleth/java-idp/trunk@1160 ab3bd59b-922f-494d-bb5f-6f0a3c29deca

src/edu/internet2/middleware/shibboleth/serviceprovider/ServiceProviderConfig.java [new file with mode: 0644]

diff --git a/src/edu/internet2/middleware/shibboleth/serviceprovider/ServiceProviderConfig.java b/src/edu/internet2/middleware/shibboleth/serviceprovider/ServiceProviderConfig.java
new file mode 100644 (file)
index 0000000..c2d20e2
--- /dev/null
@@ -0,0 +1,1234 @@
+/*
+ * ServiceProviderConfig.java
+ * 
+ * A ServiceProviderConfig object holds an instance of the Shibboleth
+ * configuration data from the main configuration file and from all
+ * secondary files referenced by the main file.
+ * 
+ * The configuration file is typically processed during Context 
+ * initialization. In a Servlet environment, this is done from
+ * the ServletContextInitializer, while in JUnit testing it is 
+ * done during test setup (unless you are testing configuration
+ * in which case it is part of the test itself). This occurs 
+ * during init() processing and is inheritly synchronized.
+ * 
+ * Initialization is a two step process. First create an
+ * instance of this class, then find a path to the configuration
+ * file and call loadConfigObjects().
+ * 
+ * In addition to the option of loading external classes that
+ * implement one of the Plugin service interfaces by providing
+ * the name of the class in the type= attribute of the Plugin
+ * configuration XML, there is also a manual wiring interface.
+ * Create an implimenting object yourself, then add it to the
+ * configuration by passing an identifying URI and the object 
+ * to a addOrReplaceXXXImplementation() method.
+ * 
+ * These wiring calls are agressive, while the XML is passive.
+ * If the wiring call is made before loadConfigObject(), then
+ * XML referencing this same URI will find that it has already
+ * been loaded and use it. Alternately, if the call is made
+ * after loadConfigObject() then the XML will have processed
+ * the URI, loaded the file, and built an implimenting object.
+ * However, the wiring call will replace that object with the
+ * one provided in the call. Obviously making these calls 
+ * first will be slightly more efficient, and is necessary if
+ * the XML configuration specifies URIs that will be provided
+ * by the wiring call and are not represented by any actual file.
+ * 
+ * After initialization completes, this object and its arrays
+ * and collections should be structurally immutable. A map or
+ * array should not be changed by adding or removing members.
+ * Thus iteration over the collections can be unsynchronized.
+ * The reference to a value in the map can be swapped for a 
+ * new object, because this doesn't effect iteration.
+ * 
+ * Any method may obtain a copy of the current ServiceProviderConfig
+ * object by calling ServiceProviderContext.getServiceProviderConfig().
+ * This reference should only be held locally (for the duration
+ * of the request). Should the entire Shibboleth configuration file 
+ * be reparsed (because of a dynamic update), then a new reference will
+ * be stored in the SPContext. Picking up a new reference for each
+ * request ensures that a program uses the latest configuration.
+ * 
+ * When a secondary file (Metadata, Trust, AAP, etc.) is reloaded,
+ * a new object is constructed for that interface and the entry in
+ * the corresponding Map of providers of that interface is replaced.
+ * Therefore, non-local variables must only store the URI for such
+ * objects. A method can pass the URI to the Map lookup method and
+ * obtain a local variable reference to the current implementing
+ * object which can be used during the processing of the current
+ * request. 
+ * 
+ * Note: The URI for a secondary file cannot change just by 
+ * reloading the file, but it can change if this main configuration
+ * file object is rebuilt. Therefore, if an external object stores
+ * a URI for a plugin object, it must be prepared for the Map lookup
+ * to return null. This would indicate that the main configuration 
+ * file has been reloaded and the previously valid URI now no longer
+ * points to any implementing object.
+ * 
+ * XML configuration data is parsed into two formats. First, it 
+ * is processed by an ordinary JAXP XML parser into a W3C DOM format.
+ * The parser may validate the XML against an XSD schema, but the 
+ * resulting DOM is "untyped". The XML becomes a tree of Element,
+ * Attribute, and Text nodes. The program must still convert an
+ * attribute or text string to a number, date, boolean, or any other
+ * data type even through the XSD declares that it must be of that 
+ * type. The program must also search through the elements of the tree
+ * to find specific names for expected contents.
+ * 
+ * This module then subjects the DOM to a secondary parse through
+ * some classes generated by compiling the XSD file with tools 
+ * provided by the Apache XML Bean project. This turns the valid
+ * XML into strongly typed Java objects. A class is generated to
+ * represent every data type defined in the XSD schemas. Attribute
+ * values and child elements become properties of the objects of
+ * these classes. The XMLBean objects simplify the configuration 
+ * logic.
+ * 
+ * If the configuration file directly reflected the program logic,
+ * the XML Bean classes would probably be enough. However, there
+ * are two important considerations:
+ * 
+ * First, the Metadata is in transition. Currently we support an
+ * ad-hoc format defined by Shibboleth. However, it is expected
+ * that this will change in the next release to support a new
+ * standard accompanying SAML 2.0. The program logic anticipates
+ * this change, and is largely designed around concepts and 
+ * structures of the new SAML standard. The actual configuration 
+ * file and XML Beans support the old format, which must be mapped
+ * into this new structure.
+ * 
+ * Second, secondary configuration elements (Credentials, Trust,
+ * Metadata, AAP, etc.) are "Pluggable" components. There is a 
+ * built-in implementation of these services based on the XML
+ * configuration described in the Shibboleth documentation. 
+ * However, the administrator can provide other classes that 
+ * implement the required interfaces by simply coding the class
+ * name in the type= parameter of the XML element referencing the
+ * plugin. In this case we don't know the format of the opaque
+ * XML and simply pass it to the plugin. 
+ * 
+ * 
+ * Dependencies: Requires XML Beans and the generated classes.
+ * Requires access to XSD schema files for configuration file formats.
+ * Logic depends on the order of enums in the XSD files.
+ * 
+ * Error Strategy: A failure parsing the main configuration file
+ * prevents further processing. However, a problem parsing a plug-in
+ * configuration file should be noted while processing continues.
+ * This strategy reports all the errors in all the files to the log
+ * rather than stopping at the first error.
+ * 
+ * --------------------
+ * Copyright 2002, 2004 
+ * University Corporation for Advanced Internet Development, Inc. 
+ * All rights reserved
+ * [Thats all we have to say to protect ourselves]
+ * Your permission to use this code is governed by "The Shibboleth License".
+ * A copy may be found at http://shibboleth.internet2.edu/license.html
+ * [Nothing in copyright law requires license text in every file.]
+ */
+
+package edu.internet2.middleware.shibboleth.serviceprovider;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.log4j.Logger;
+import org.apache.xerces.parsers.DOMParser;
+import org.apache.xmlbeans.XmlException;
+import org.apache.xmlbeans.XmlOptions;
+import org.opensaml.SAMLAssertion;
+import org.opensaml.SAMLAttribute;
+import org.opensaml.SAMLAttributeStatement;
+import org.opensaml.SAMLObject;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import x0.maceShibboleth1.AttributeAcceptancePolicyDocument;
+import x0.maceShibbolethTargetConfig1.ApplicationDocument;
+import x0.maceShibbolethTargetConfig1.PluggableType;
+import x0.maceShibbolethTargetConfig1.RequestMapDocument;
+import x0.maceShibbolethTargetConfig1.ShibbolethTargetConfigDocument;
+import x0.maceShibbolethTargetConfig1.ApplicationDocument.Application;
+import x0.maceShibbolethTargetConfig1.ApplicationsDocument.Applications;
+import x0.maceShibbolethTargetConfig1.HostDocument.Host;
+import x0.maceShibbolethTargetConfig1.PathDocument.Path;
+import x0.maceShibbolethTargetConfig1.SHIREDocument.SHIRE;
+import x0.maceShibbolethTargetConfig1.ShibbolethTargetConfigDocument.ShibbolethTargetConfig;
+import edu.internet2.middleware.shibboleth.common.AAP;
+import edu.internet2.middleware.shibboleth.common.AttributeRule;
+import edu.internet2.middleware.shibboleth.common.Credentials;
+import edu.internet2.middleware.shibboleth.common.ShibResource;
+import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
+import edu.internet2.middleware.shibboleth.common.XML;
+import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
+import edu.internet2.middleware.shibboleth.metadata.EntityLocator;
+import edu.internet2.middleware.shibboleth.metadata.Metadata;
+import edu.internet2.middleware.shibboleth.metadata.Provider;
+import edu.internet2.middleware.shibboleth.metadata.ProviderRole;
+
+/**
+ * Load the configuration files into objects, index them, and return them on request.
+ * 
+ * <p>A new instance of the ServiceProviderConfig object can be created to 
+ * parse a new version of the configuration file. It can then be swapped
+ * into the ServiceProviderContext reference and will be picked up by
+ * subsequent requests.</p>
+ * 
+ * @author Howard Gilbert
+ */
+public class ServiceProviderConfig {
+
+       
+       private static final String INLINEURN = "urn:inlineBS:ID";
+    private static Logger log = Logger.getLogger(ServiceProviderConfig.class);
+
+       private ShibbolethTargetConfig  // The XMLBean from the main config file
+               config = null;              // (i.e. shibboleth.xml)
+       
+       
+       /*
+        * The following Maps reference objects that implement a plugin
+        * interface indexed by their URI. There are builtin objects
+        * created from inline or external XML files, but external 
+        * objects implementing the interfaces may be injected by 
+        * calling the addOrReplaceXXX method. Public access to these
+        * Maps is indirect, through methods the ApplicationInfo object
+        * for a given configured or default application.
+        */
+       
+       // Note EntityLocator extends and renames the old "Metadata" interface
+       private Map/*<String, EntityLocator>*/ entityLocators = 
+               new TreeMap/*<String, EntityLocator>*/();
+       
+       public void addOrReplaceMetadataImplementor(String uri, EntityLocator m) {
+           entityLocators.put(uri, m);
+       }
+       
+       public EntityLocator getMetadataImplementor(String uri) {
+           return (EntityLocator) entityLocators.get(uri);
+       }
+       
+       private Map/*<String, AAP>*/ attributePolicies = 
+               new TreeMap/*<String, AAP>*/();
+       
+       public void addOrReplaceAAPImplementor(String uri, AAP a) {
+           attributePolicies.put(uri,a);
+       }
+       
+       public AAP getAAPImplementor(String uri) {
+           return (AAP) attributePolicies.get(uri);
+       }
+       
+       private Map/*<String, ITrust>*/ certificateValidators = 
+               new TreeMap/*<String, ITrust>*/();
+       
+       public void addOrReplaceTrustImplementor(String uri, ITrust t) {
+           certificateValidators.put(uri,t);
+       }
+       
+       public ITrust getTrustImplementor(String uri) {
+           return (ITrust) certificateValidators.get(uri);
+       }
+       
+       
+       /*
+        * Objects created from the <Application(s)> elements.
+        * They manage collections of URI-Strings that index the
+        * previous maps to return Metadata, Trust, and AAP info
+        * applicable to this applicationId.
+        */
+       private Map/*<String, ApplicationInfo>*/ applications = 
+               new TreeMap/*<String, ApplicationInfo>*/();
+       
+       // Default application info from <Applications> element
+       private ApplicationInfo defaultApplicationInfo = null;
+
+    public ApplicationInfo getApplication(String applicationId) {
+       ApplicationInfo app=null;
+       app = (ApplicationInfo) applications.get(applicationId);
+       if (app==null)  // If no specific match, return default
+               return defaultApplicationInfo;
+       return app;
+    }
+       
+       
+       // Objects created from single configuration file elements
+       private Credentials credentials = null;
+       private RequestMapDocument.RequestMap requestMap = null;
+       
+       
+       /*
+        * A few constants
+        */
+       private final String SCHEMADIR = "/schemas/";
+       private final String MAINSCHEMA = SCHEMADIR + XML.MAIN_SHEMA_ID;
+       private final String METADATASCHEMA = SCHEMADIR + XML.SHIB_SCHEMA_ID;
+       private final String TRUSTSCHEMA = SCHEMADIR + XML.TRUST_SCHEMA_ID;
+       private final String AAPSCHEMA = SCHEMADIR + XML.SHIB_SCHEMA_ID;
+
+       private static final String XMLTRUSTPROVIDERTYPE = 
+               "edu.internet2.middleware.shibboleth.common.provider.XMLTrust";
+       private static final String XMLAAPPROVIDERTYPE = 
+               "edu.internet2.middleware.shibboleth.target.provider.XMLAAP";
+       private static final String XMLFEDERATIONPROVIDERTYPE = 
+               "edu.internet2.middleware.shibboleth.common.provider.XMLMetadata";
+       private static final String XMLREVOCATIONPROVIDERTYPE =
+           "edu.internet2.middleware.shibboleth.common.provider.XMLRevocation";
+       private static final String XMLREQUESTMAPPROVIDERTYPE = 
+           "edu.internet2.middleware.shibboleth.target.provider.XMLRequestMap";
+       private static final String XMLCREDENTIALSPROVIDERTYPE = 
+           "edu.internet2.middleware.shibboleth.common.Credentials";
+       
+       
+       
+       private DOMParser parser = new DOMParser();
+       
+       /**
+        * The constructor prepares for, but does not parse the configuration.
+        * 
+        * @throws ShibbolethConfigurationException
+        *        if XML Parser cannot be initialized (Classpath problem)
+        */
+       public ServiceProviderConfig() 
+               throws ShibbolethConfigurationException {
+               try {
+               parser.setFeature("http://xml.org/sax/features/validation", true);
+               parser.setFeature("http://apache.org/xml/features/validation/schema", true);
+        
+        
+               parser.setErrorHandler(new ErrorHandler() {
+        
+                       public void error(SAXParseException arg0) throws SAXException {
+                               throw new SAXException("Error parsing xml file: " + arg0);
+                       }
+        
+                       public void fatalError(SAXParseException arg0) throws SAXException {
+                               throw new SAXException("Error parsing xml file: " + arg0);
+                       }
+        
+                       public void warning(SAXParseException arg0) throws SAXException {
+                               throw new SAXException("Error parsing xml file: " + arg0);
+                       }
+               });
+        
+        } catch (SAXException e) {
+               log.error("Unable to setup a workable XML parser: " + e);
+               throw new ShibbolethConfigurationException("Unable to setup a workable XML parser.");
+        }
+       }
+
+       /**
+        * loadConfigObjects must be called once to parse the configuration.
+        * 
+        * <p>To reload a modified configuration file, create and load a second 
+        * object and swap the reference in the context object.</p>
+        * 
+        * @param configFilePath URL or resource name of file
+        * @return the DOM Document
+        * @throws ShibbolethConfigurationException
+        *             if there was an error loading the file
+        */
+       public synchronized void loadConfigObjects(String configFilePath)
+                       throws ShibbolethConfigurationException {
+           
+           if (config!=null) {
+                       log.error("ServiceProviderConfig.loadConfigObjects may not be called twice for the same object.");
+                       throw new ShibbolethConfigurationException("Cannot reload configuration into same object.");
+               }
+
+               Document configDoc;
+        try {
+            configDoc = loadDom(configFilePath, MAINSCHEMA);
+        } catch (InternalConfigurationException e) {
+            throw new ShibbolethConfigurationException("XML error in "+configFilePath);
+        }
+        loadConfigBean(configDoc);
+
+               return;
+       }
+       
+       /*
+        * Given a URL, determine its ApplicationId from the RequestMap config.
+        * 
+        * <p>Note: This is not a full implementation of all RequestMap
+        * configuration options. Other functions will be added as needed.</p>
+        */
+       public String mapRequest(String urlreq) {
+           String applicationId = "default";
+           URL url;
+           
+           try {
+            url = new URL(urlreq);
+        } catch (MalformedURLException e) {
+            return applicationId;
+        }
+        
+        String urlscheme = url.getProtocol();
+        String urlhostname = url.getHost();
+        String urlpath = url.getPath();
+        int urlport = url.getPort();
+        if (urlport==0) {
+            if (urlscheme.equals("http"))
+                urlport=80;
+            else if (urlscheme.equals("https"))
+                urlport=443;
+        }
+        
+        // find Host entry for this virtual server
+        Host[] hostArray = requestMap.getHostArray();
+        for (int ihost=0;ihost<hostArray.length;ihost++) {
+            Host host = hostArray[ihost];
+            String hostScheme = host.getScheme().toString();
+            String hostName = host.getName();
+            String hostApplicationId = host.getApplicationId();
+            long hostport = host.getPort();
+            if (hostport==0) {
+                if (hostScheme.equals("http"))
+                    hostport=80;
+                else if (hostScheme.equals("https"))
+                    hostport=443;
+            }
+            
+            if (!urlscheme.equals(hostScheme) ||
+                !urlhostname.equals(hostName)||
+                urlport!=hostport)
+                continue;
+            
+            // find Path entry for this subdirectory
+            Path[] pathArray = host.getPathArray();
+            if (hostApplicationId!=null)
+                applicationId=hostApplicationId;
+            for (int i=0;i<pathArray.length;i++) {
+                String dirname = pathArray[i].getName();
+                if (urlpath.equals(dirname)||
+                    urlpath.startsWith(dirname+"/")){
+                    String pthid= pathArray[i].getApplicationId();
+                    if (pthid!=null)
+                        applicationId=pthid;
+                }
+            }
+        }
+           
+           return applicationId;
+       }
+
+       /**
+        * <p>Parse the main configuration file DOM into XML Bean</p>
+        * 
+        * <p>Automatically load secondary configuration files designated
+        * by URLs in the main configuration file</p>
+        * 
+        * @throws ShibbolethConfigurationException
+        */
+       private void loadConfigBean(Document configDoc) 
+               throws ShibbolethConfigurationException {
+           boolean anyError=false;
+               ShibbolethTargetConfigDocument configBeanDoc;
+        try {
+                       // reprocess the already validated DOM to create a bean with typed fields
+                       // dump the trash (comments, processing instructions, extra whitespace)
+                       configBeanDoc = ShibbolethTargetConfigDocument.Factory.parse(configDoc,
+                               new XmlOptions().setLoadStripComments().setLoadStripProcinsts().setLoadStripWhitespace());
+                       config=configBeanDoc.getShibbolethTargetConfig();
+               } catch (XmlException e) {
+                       // Since the DOM was already validated against the schema, errors will not typically occur here
+                       log.error("Error while parsing shibboleth configuration");
+                       throw new ShibbolethConfigurationException("Error while parsing shibboleth configuration");
+               }
+               
+               // Extract the "root Element" object from the "Document" object
+               ShibbolethTargetConfig config = configBeanDoc.getShibbolethTargetConfig();
+               
+               Applications apps = config.getApplications(); // <Applications>
+               
+               
+               
+               /*
+                * Create an <Application> id "default" from <Applications>
+                */
+               ApplicationDocument defaultAppDoc = 
+                   // Create a new XMLBeans "Document" level object
+                       ApplicationDocument.Factory.newInstance(); 
+               ApplicationDocument.Application defaultApp = 
+                   // Add an XMLBeans "root Element" object to the Document
+                       defaultAppDoc.addNewApplication();
+               // set or copy over fields from unrelated Applications object
+               defaultApp.setId("default");
+               defaultApp.setAAPProviderArray(apps.getAAPProviderArray());
+               defaultApp.setAttributeDesignatorArray(apps.getAttributeDesignatorArray());
+               defaultApp.setAudienceArray(apps.getAudienceArray());
+               defaultApp.setCredentialUse(apps.getCredentialUse());
+               defaultApp.setErrors(apps.getErrors());
+               defaultApp.setFederationProviderArray(apps.getFederationProviderArray());
+               defaultApp.setProviderId(apps.getProviderId());
+               defaultApp.setRevocationProviderArray(apps.getRevocationProviderArray());
+               defaultApp.setSessions(apps.getSessions());
+               defaultApp.setSignedAssertions(apps.getSignedAssertions());
+               defaultApp.setSignedResponse(apps.getSignedResponse());
+               defaultApp.setSignRequest(apps.getSignRequest());
+               defaultApp.setTrustProviderArray(apps.getTrustProviderArray());
+               
+               /*
+                * Now process secondary files configured in the applications
+                */
+               anyError |= processApplication(defaultApp);
+               
+               Application[] apparray = apps.getApplicationArray();
+               for (int i=0;i<apparray.length;i++){
+                       Application tempapp = apparray[i];
+                       applications.put(tempapp.getId(),tempapp);
+                       anyError |= processApplication(tempapp);
+               }
+               
+               /*
+                * Now process other secondary files
+                */
+               anyError |= processCredentials();
+               anyError |= processPluggableRequestMapProvider();
+               
+               if (anyError)
+                   throw new ShibbolethConfigurationException("Errors processing configuration file, see log");
+       }
+
+       
+       /**
+        * Routine to handle CredentialProvider
+        * 
+        * <p>Note: This only handles in-line XML.
+        * Also, Credentials was an existing Origin class, so it doesn't
+        * implement the new PluggableConfigurationComponent interface and
+        * can't be loaded by generic plugin support.
+        * </p>
+        */
+       private boolean processCredentials() {
+           boolean anyError=false;
+           PluggableType[] pluggable = config.getCredentialsProviderArray();
+           for (int i=0;i<pluggable.length;i++) {
+                       String pluggabletype = pluggable[i].getType();
+               if (!pluggabletype.equals(
+                       "edu.internet2.middleware.shibboleth.common.Credentials")) {
+                               log.error("Unsupported CredentialsProvider type "+pluggabletype);
+                               anyError=true;
+                               continue;
+               }
+               PluggableType credentialsProvider = pluggable[i];
+            Node fragment = credentialsProvider.newDomNode();
+            // newDomNode returns the current node wrapped in a Fragment
+            try {
+                Node credentialsProviderNode = fragment.getFirstChild();
+                Node credentialsNode=credentialsProviderNode.getFirstChild();
+                credentials = new Credentials((Element)credentialsNode);
+            } catch(Exception e) {
+                log.error("Cannot process Credentials element of Shibboleth configuration");
+                log.error(e);
+                anyError=true;
+                continue;
+            }
+           }
+           return anyError;
+       }
+
+    /**
+        * Find and load secondary configuration files referenced in an Application(s) 
+        * 
+        * @param app Application object
+        * @throws ShibbolethConfigurationException
+        */
+       private boolean processApplication(Application app) 
+               throws ShibbolethConfigurationException {
+           
+           boolean anyError=false;
+           
+           String applicationId = app.getId();
+               
+               ApplicationInfo appinfo = new ApplicationInfo(app);
+               
+               anyError |= processPluggableMetadata(appinfo);
+               anyError |= processPluggableAAPs(appinfo);
+               anyError |= processPluggableTrusts(appinfo);
+               
+               applications.put(applicationId, appinfo);
+               
+               return anyError;
+       }
+
+    /**
+     * Generic code to create an object of a Pluggable type that implements
+     * a configuration interface.
+     * 
+     * <p>The configuration schema defines "PluggableType" as a type of
+     * XML element that has opaque contents and attributes "type" and 
+     * "uri". If the uri attribute is omitted, then the configuration
+     * data is inline XML content. The XSD files typically define the
+     * format of pluggable configuration elements, but without binding
+     * them to the PluggableType element that may contain them.</p>
+     * 
+     * <p>The implimentation of pluggable objects is provided by 
+     * external classes. There are "builtin" classes provided with
+     * Shibboleth (XMLMetadataImpl, XMLTrustImpl, XMLAAPImpl) that 
+     * provide examples of how this is done. By design, others can
+     * provide their own classes just by putting the class name as
+     * the value of the type attribute.</p>
+     * 
+     * <p>This routine handles the common setup. It creates objects
+     * of one of the builtin types, or it uses Class.forName() to
+     * access a user specified class. It then locates either the
+     * inline XML elements or the external XML file. It passes the
+     * XML to initialize the object. Finally, a reference to the 
+     * object is stored in the appropriate Map.</p>
+     * 
+     * <p>The objects created implement two interfaces. Mostly they
+     * implement a configuration interface (EntityDescriptor, ITrust,
+     * AAP, etc). However, for the purpose of this routine they also
+     * must be declared to implement PluggableConfigurationComponent
+     * and provide an initialize() method that parses a DOM Node 
+     * containing their root XML configuration element.</p>
+     * 
+     * @param pluggable XMLBean for element defined in XSD to be of "PluggableType"
+     * @param implclass java.lang.Class of Builtin implementation class
+     * @param interfaceClass java.lang.Class of Interface
+     * @param builtinName alias type to choose Builtin imlementation
+     * @param uriMap ApplicationInfo Map for this interface
+     * @return
+     */
+    private 
+       String 
+    processPluggable(
+            PluggableType pluggable,
+               Class implclass,
+               Class interfaceClass,
+               String builtinName,
+               String schemaname,
+               Map /*<String,PluggableConfigurationComponent>*/uriMap
+               ) {
+        
+       String pluggabletype = pluggable.getType();
+       
+       if (!pluggabletype.equals(builtinName)) {
+           // Not the builtin type, try to load user class by name
+           try {
+                implclass = Class.forName(pluggabletype);
+            } catch (ClassNotFoundException e) {
+                       log.error("Type value "+pluggabletype+" not found as supplied Java class");
+                   return null;
+            }
+           if (!interfaceClass.isAssignableFrom(implclass)||
+                !PluggableConfigurationComponent.class.isAssignableFrom(implclass)) {
+                       log.error(pluggabletype+" class does not support required interfaces.");
+                   return null;
+           }
+       }
+       
+       PluggableConfigurationComponent impl;
+        try {
+            impl = (PluggableConfigurationComponent) implclass.newInstance();
+        } catch (Exception e) {
+            log.error("Unable to instantiate "+pluggabletype);
+            return null;
+        }
+       
+       String uri = pluggable.getUri();
+       if (uri==null) { // inline
+           
+               uri=genDummyUri();
+               try {
+                       Node fragment = pluggable.newDomNode();        // XML-Fragment node
+                       Node pluggableNode = fragment.getFirstChild(); // PluggableType 
+                       Node contentNode=pluggableNode.getFirstChild();// root element
+                       impl.initialize(contentNode);
+               } catch (Exception e) {
+                       log.error("XML error " + e);
+                       return null;
+               }
+               
+       } else { // external file
+               
+               if (uriMap.get(uri)!=null) { // Already parsed this file
+                   return "";
+               }
+               
+            String tempname = impl.getSchemaPathname();
+            if (tempname!=null)
+                schemaname=tempname;
+            
+               try {
+                       Document extdoc = loadDom(uri,schemaname);
+                       impl.initialize(extdoc);
+               } catch (Exception e) {
+                       log.error("XML error " + e);
+                       return null;
+               }
+       }
+       
+       uriMap.put(uri,impl);
+       return uri;
+    }
+       
+       
+
+       /**
+        * Handle a FederationProvider 
+        */
+       private boolean processPluggableMetadata(ApplicationInfo appinfo) {
+           boolean anyError = false;
+               PluggableType[] pluggable = appinfo.getApplicationConfig().getFederationProviderArray();
+               for (int i = 0;i<pluggable.length;i++) {
+                   String uri = processPluggable(pluggable[i],
+                           XMLMetadataImpl.class,
+                           EntityLocator.class,
+                           XMLFEDERATIONPROVIDERTYPE,
+                           METADATASCHEMA,
+                           entityLocators);
+                   if (uri==null)
+                       anyError=true;
+                   else if (uri.length()>0) {
+                               appinfo.addGroupUri(uri);
+                   }
+               }
+               return anyError;
+       }
+       
+       /**
+        * Reload XML Metadata configuration after file changed.
+        * @param uri Path to Metadata XML configuration
+        * @return true if file reloaded.
+        */
+       public boolean reloadFederation(String uri) {
+           if (getMetadataImplementor(uri)!=null||
+                   uri.startsWith(INLINEURN))
+               return false;
+               try {
+                       Document sitedoc = loadDom(uri,METADATASCHEMA);
+                       XMLMetadataImpl impl = new XMLMetadataImpl();
+                       impl.initialize(sitedoc);
+                       addOrReplaceMetadataImplementor(uri,impl);
+               } catch (Exception e) {
+                       log.error("Error while parsing Metadata file "+uri);
+                       log.error("XML error " + e);
+                       return false;
+               }
+           return true;
+       }
+
+       /**
+        * Handle an AAPProvider element with
+        *      type="edu.internet2.middleware.shibboleth.common.provider.XMLAAP"
+        * @throws InternalConfigurationException
+        */
+       private boolean processPluggableAAPs(ApplicationInfo appinfo){
+           boolean anyError=false;
+               PluggableType[] pluggable = appinfo.getApplicationConfig().getAAPProviderArray();
+               for (int i = 0;i<pluggable.length;i++) {
+                   String uri = processPluggable(pluggable[i],
+                           XMLAAPImpl.class,
+                           AAP.class,
+                           XMLAAPPROVIDERTYPE,
+                           AAPSCHEMA,
+                           attributePolicies);
+                   if (uri==null)
+                       anyError=true;
+                   else if (uri.length()>0) {
+                               appinfo.addAapUri(uri);
+                   }
+               }
+               return anyError;
+       }
+       
+       /**
+        * Reload XML AAP configuration after file changed.
+        * @param uri AAP to Trust XML configuration
+        * @return true if file reloaded.
+        */
+       public boolean reloadAAP(String uri) {
+           if (getAAPImplementor(uri)!=null||
+                   uri.startsWith(INLINEURN))
+               return false;
+               try {
+                       Document aapdoc = loadDom(uri,AAPSCHEMA);
+                       AttributeAcceptancePolicyDocument aap = AttributeAcceptancePolicyDocument.Factory.parse(aapdoc);
+                       XMLAAPImpl impl = new XMLAAPImpl();
+                       impl.initialize(aapdoc);
+                       addOrReplaceAAPImplementor(uri,impl);
+               } catch (Exception e) {
+                       log.error("Error while parsing AAP file "+uri);
+                       log.error("XML error " + e);
+                       return false;
+               }
+           return true;
+       }
+       
+       
+       /**
+        * Handle a TrustProvider element with
+        *      type="edu.internet2.middleware.shibboleth.common.provider.XMLTrust"
+        * 
+        * Note: This code builds the semantic structure of trust. That is, it knows
+        * about certificates and keys. The actual logic of Trust (signature generation
+        * and validation) is implemented in a peer object defined in the external
+        * class XMLTrustImpl.
+        * 
+        * @throws ShibbolethConfigurationException if X.509 certificate cannot be processed 
+        * @throws InternalConfigurationException
+        */
+       private boolean processPluggableTrusts(ApplicationInfo appinfo){
+           boolean anyError=false;
+               PluggableType[] pluggable = appinfo.getApplicationConfig().getTrustProviderArray();
+               for (int i = 0;i<pluggable.length;i++) {
+                   String uri = processPluggable(pluggable[i],
+                           XMLTrustImpl.class,
+                           ITrust.class,
+                           XMLTRUSTPROVIDERTYPE,
+                           TRUSTSCHEMA,
+                           certificateValidators);
+                   if (uri==null)
+                       anyError=true;
+                   else if (uri.length()>0) {
+                               appinfo.addTrustUri(uri);
+                   }
+               }
+               return anyError;
+       }
+
+       /**
+        * Reload XML Trust configuration after file changed.
+        * @param uri Path to Trust XML configuration
+        * @return true if file reloaded.
+        */
+       public boolean reloadTrust(String uri) {
+           if (getTrustImplementor(uri)!=null||
+                   uri.startsWith(INLINEURN))
+               return false;
+               try {
+                       Document trustdoc = loadDom(uri,TRUSTSCHEMA);
+                       XMLTrustImpl impl = new XMLTrustImpl();
+                       impl.initialize(trustdoc);
+                       addOrReplaceTrustImplementor(uri,impl);
+               } catch (Exception e) {
+                       log.error("Error while parsing Trust file "+uri);
+                       log.error("XML error " + e);
+                       return false;
+               }
+           return true;
+       }
+       
+       
+       private boolean processPluggableRequestMapProvider(){
+           SHIRE shire = config.getSHIRE();
+           PluggableType mapProvider = shire.getRequestMapProvider();
+           
+           String pluggabletype = mapProvider.getType();
+           if (!pluggabletype.equals(XMLREQUESTMAPPROVIDERTYPE)) {
+               log.error("Unsupported RequestMapProvider type "+pluggabletype);
+               return true;
+           }
+           
+           RequestMapDocument requestMapDoc = null;
+           Document mapdoc = null;
+           Element maproot = null;
+           String uri = mapProvider.getUri();
+           
+           if (uri==null) { // inline
+               
+               uri=genDummyUri();
+               try {
+                   Node fragment = mapProvider.newDomNode();
+                   Node pluggableNode = fragment.getFirstChild();
+                   Node contentNode=pluggableNode.getFirstChild();
+                   
+                   requestMapDoc = RequestMapDocument.Factory.parse(contentNode);
+               } catch (Exception e) {
+                   log.error("Error while parsing inline RequestMap");
+                   log.error("XML error " + e);
+                   return true;
+               }
+               
+           } else { // external file
+               try {
+                   mapdoc = loadDom(uri,METADATASCHEMA);
+                   requestMapDoc = RequestMapDocument.Factory.parse(mapdoc);
+               } catch (Exception e) {
+                   log.error("Error while parsing RequestMap file "+uri);
+                   log.error("XML error " + e);
+                   return true;
+               }
+           }
+           
+           requestMap = requestMapDoc.getRequestMap();
+           return false;
+       }
+
+       
+       
+       /**
+        * Given a file URI, parse it (validated against a schema) to a DOM.
+        * 
+        * <p>Note: The exact format of the URI (absolute file path, resource
+        * path, URL) is interpreted here. Changes to URI conventions can be
+        * implemented here and apply throughout the configuration system.</p>
+        *
+        * @param configFilePath Path to the particular configuration file
+        * @param schemaFilePath Path to the schema for this type of file 
+        * @return DOM Document object created from the file
+        * @throws ShibbolethConfigurationException if file not found or invalid
+        */
+       private Document loadDom(String configFilePath, String schemaFilePath) 
+               throws InternalConfigurationException {
+               
+               log.debug("Loading Configuration from (" + configFilePath + ").");
+
+                       InputSource insrc;
+                       String schemaCannonicalFilePath;
+                       ShibResource configResource;
+                       ShibResource schemaResource;
+                       try {
+                               configResource = new ShibResource(configFilePath,this.getClass());
+                               insrc = new InputSource(configResource.getInputStream());
+                       } catch (Exception e1) {
+                               log.error("Configuration file "+configFilePath+" could not be located.");
+                               throw new InternalConfigurationException();
+                       }
+                       
+                       try {
+                               schemaResource = new ShibResource(schemaFilePath,this.getClass());
+                schemaCannonicalFilePath = schemaResource.getFile().getCanonicalPath();
+                       } catch (Exception e) {
+                               log.error("Schema file "+schemaFilePath+" could not be located.");
+                               throw new InternalConfigurationException();
+                       }
+                       
+                       
+               return loadDom(insrc,schemaCannonicalFilePath);
+               
+       }
+
+       
+       /**
+        * Parse an InputSource (maybe file, maybe buffer) into a DOM
+        * @param insrc XML InputSource object
+        * @param schemaFilePath
+        * @return DOM Document
+        * @throws ShibbolethConfigurationException
+        */
+       private Document loadDom(InputSource insrc, String schemaFilePath) 
+               throws InternalConfigurationException {
+       
+               try {   
+                       parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaSource",
+                                       schemaFilePath);
+                       
+                       parser.parse(insrc);
+                       
+               } catch (SAXException e) {
+                       log.error("Error while parsing shibboleth configuration: " + e);
+                       throw new InternalConfigurationException();
+               } catch (IOException e) {
+                       log.error("Could not load shibboleth configuration file: " + e);
+                       throw new InternalConfigurationException();
+               }
+       
+               return parser.getDocument();
+               
+       }
+
+       
+       
+
+       private int inlinenum = 1;
+       private String genDummyUri() {
+               return INLINEURN+Integer.toString(inlinenum++);
+       }
+       
+       
+       
+
+
+
+    /**
+        * ApplicationInfo represents the <Application(s)> object, its fields,
+        * and the pluggable configuration elements under it.
+        * 
+        * <p>It can return arrays of Metadata, Trust, or AAP providers, but
+        * it also exposes convenience methods that shop the lookup(),
+        * validate(), and trust() calls to each object in the collection
+        * until success or failure is determined.</p>
+        * 
+        * <p>For all other parameters, such as Session parameters, you
+        * can fetch the XMLBean by calling getApplicationConf() and 
+        * query their value directly.
+        */
+       class ApplicationInfo 
+               implements EntityLocator, ITrust {
+               
+               private Application applicationConfig;
+        public Application getApplicationConfig() {
+            return applicationConfig;
+        }
+               
+               /**
+                * Construct this object from the XML Bean.
+                * @param application XMLBean for Application element
+                */
+               ApplicationInfo(Application application) {
+                   this.applicationConfig=application;
+               }
+               
+               
+               /*
+                * Following the general rule, this object may not keep 
+                * direct references to the plugin interface implementors,
+                * but must look them up on every call through their URI keys.
+                * So we keep collections of URI strings instead.
+                */
+               ArrayList groupUris = new ArrayList();
+               ArrayList trustUris = new ArrayList();
+               ArrayList aapUris   =   new ArrayList();
+               
+        void addGroupUri(String uri) {
+                       groupUris.add(uri);
+               }
+               void addTrustUri(String uri) {
+                       trustUris.add(uri);
+               }
+               void addAapUri(String uri) {
+                       aapUris.add(uri);
+               }
+               
+               /**
+                * Return the current array of objects that implement the
+                * ...metadata.Metadata interface
+                * 
+                * @return Metadata[]
+                */
+               Metadata[] getMetadataProviders() {
+                       Iterator iuris = groupUris.iterator();
+                       int count = groupUris.size();
+                       Metadata[] metadatas = new Metadata[count];
+                       for (int i=0;i<count;i++) {
+                               String uri =(String) iuris.next();
+                               metadatas[i]=getMetadataImplementor(uri);
+                       }
+                       return metadatas;
+               }
+               
+               /**
+                * A convenience function based on the Metadata interface.
+                * 
+                * <p>Look for an entity ID through each implementor until the
+                * first one finds locates a describing object.</p>
+                * 
+                * <p>Unfortunately, Metadata.lookup() was originally specified to
+                * return a "Provider". In current SAML 2.0 terms, the object
+                * returned should be an EntityDescriptor. So this is the new 
+                * function in the new interface that will use the new term, but
+                * it does the same thing.</p>
+                *  
+                * @param id ID of the OriginSite entity
+                * @return EntityDescriptor metadata object for that site.
+                */
+               public EntityDescriptor getEntityDescriptor(String id) {
+                       Iterator iuris = groupUris.iterator();
+                       while (iuris.hasNext()) {
+                               String uri =(String) iuris.next();
+                               EntityLocator locator=getMetadataImplementor(uri);
+                               EntityDescriptor entity = locator.getEntityDescriptor(id);
+                               if (entity!=null)
+                                       return entity;
+                       }
+                       return null;
+               }
+               
+               /**
+                * Convenience function to fulfill Metadata interface contract.
+                * 
+                * @param id ID of OriginSite
+                * @return Provider object for that Site.
+                */
+               public Provider lookup(String id) {
+                       return getEntityDescriptor(id);
+               }
+               
+               /**
+                * Return the current array of objects that implement the ITrust interface
+                * 
+                * @return ITrust[]
+                */
+               public ITrust[] getTrustProviders() {
+                       Iterator iuris = groupUris.iterator();
+                       int count = groupUris.size();
+                       ITrust[] trusts = new ITrust[count];
+                       for (int i=0;i<count;i++) {
+                               String uri =(String) iuris.next();
+                               trusts[i]=getTrustImplementor(uri);
+                       }
+                       return trusts;
+               }
+               
+               /**
+                * Return the current array of objects that implement the AAP interface
+                * 
+                * @return AAP[]
+                */
+               public AAP[] getAAPProviders() {
+                       Iterator iuris = aapUris.iterator();
+                       int count = aapUris.size();
+                       AAP[] aaps = new AAP[count];
+                       for (int i=0;i<count;i++) {
+                               String uri =(String) iuris.next();
+                               aaps[i]=getAAPImplementor(uri);
+                       }
+                       return aaps;
+               }
+               
+               /**
+                * Convenience function to apply AAP by calling the apply()
+                * method of each AAP implementor.
+                * 
+                * <p>Any AAP implementor can delete an assertion or value.
+                * Empty SAML elements get removed from the assertion.
+                * This can yield an AttributeAssertion with no attributes. 
+                * 
+                * @param entity     Origin site that sent the assertion
+                * @param assertion  SAML Attribute Assertion
+                */
+               void applyAAP(EntityDescriptor entity, SAMLAssertion assertion) {
+                   
+                   // Foreach AAP in the collection
+                       AAP[] providers = getAAPProviders();
+                       for (int i=0;i<providers.length;i++) {
+                               AAP aap = providers[i];
+                               if (aap.isAnyAttribute())
+                                       continue;
+                               
+                               // Foreach Statement in the Assertion
+                               Iterator statements = assertion.getStatements();
+                               int istatement=0;
+                               while (statements.hasNext()) {
+                                       Object statement = statements.next();
+                                       if (statement instanceof SAMLAttributeStatement) {
+                                               SAMLAttributeStatement attributeStatement = 
+                                                       (SAMLAttributeStatement) statement;
+                                               
+                                               // Foreach Attribute in the AttributeStatement
+                                               Iterator attributes = attributeStatement.getAttributes();
+                                               int iattribute=0;
+                                               while (attributes.hasNext()) {
+                                                       SAMLAttribute attribute = 
+                                                               (SAMLAttribute) attributes.next();
+                                                       String name = attribute.getName();
+                                                       String namespace = attribute.getNamespace();
+                                                       AttributeRule rule = aap.lookup(name,namespace);
+                                                       if (rule==null) {
+                                                               // TODO Not sure, but code appears to keep unknown attributes
+                                                               log.warn("No rule found for attribute "+name);
+                                                               iattribute++;
+                                                               continue;
+                                                       }
+                                                       rule.apply(entity,attribute);
+                                                       if (!attribute.getValues().hasNext())
+                                                               attributeStatement.removeAttribute(iattribute);
+                                                       else
+                                                               iattribute++;
+                                                               
+                                               }
+                                               if (!attributeStatement.getAttributes().hasNext())
+                                                       assertion.removeStatement(istatement);
+                                               else
+                                                       istatement++;
+                                       } else {
+                                               istatement++;
+                                       }
+                               }
+                       }
+               }
+               
+               
+               /**
+                * Returns a collection of attribute names to request from the AA.
+                * 
+                * @return Collection of attribute Name values
+                */
+               public Collection getAttributeDesignators() {
+                       // TODO Not sure where this should come from
+                       return new ArrayList();
+               }
+
+               
+               /**
+                * Convenience method implementing ITrust.validate() across 
+                * the collection of implementing objects. Returns true if
+                * any Trust implementor approves the signatures in the object.
+                * 
+                * <p>In the interface, validate() is passed several arguments
+                * that come from this object. In this function, those
+                * arguments are ignored "this" is used. 
+                */
+               public boolean 
+               validate(
+                               Iterator revocations,  // Currently unused 
+                               ProviderRole role,
+                               SAMLObject token, 
+                               EntityLocator dummy    // "this" is an EntityLocator 
+                                       ) {
+                       
+                       // TODO If revocations are supported, "this" will provide them
+                       
+                       ITrust[] trustProviders = getTrustProviders();
+                       for (int i=0;i<trustProviders.length;i++) {
+                               ITrust trust = trustProviders[i];
+                               if (trust.validate(null,role,token,this))
+                                       return true;
+                       }
+                       return false;
+               }
+               
+               /**
+                * Simpler version of validate that avoids dummy arguments
+                * 
+                * @param role  Entity that sent Token (from Metadata)
+                * @param token Signed SAMLObject
+                * @return
+                */
+               public boolean validate(ProviderRole role, SAMLObject token) {
+                       return validate(null,role,token,null);
+               }
+
+               /**
+                * A method of ITrust that we must declare to claim that 
+                * ApplicationInfo implements ITrust. However, no code in the
+                * ServiceProvider calls this (probably an Origin thing).
+                * 
+                * @param revocations
+                * @param role
+                * @return  This dummy always returns false.
+                */
+               public boolean attach(Iterator revocations, ProviderRole role) {
+                       // Unused
+                       return false;
+               }
+               
+       }
+       
+
+       
+       private static class InternalConfigurationException extends Exception {
+           InternalConfigurationException() {
+               super();
+           }
+       }
+       
+}