Integrated SCT's attribute definition classes.
authorwassa <wassa@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Mon, 16 May 2005 18:07:35 +0000 (18:07 +0000)
committerwassa <wassa@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Mon, 16 May 2005 18:07:35 +0000 (18:07 +0000)
git-svn-id: https://subversion.switch.ch/svn/shibboleth/java-idp/trunk@1487 ab3bd59b-922f-494d-bb5f-6f0a3c29deca

29 files changed:
data/attr-composite.datafile.1 [new file with mode: 0644]
data/attr-composite.output.1 [new file with mode: 0644]
data/attr-composite.resolver.1.xml [new file with mode: 0644]
data/attr-format.datafile.1 [new file with mode: 0644]
data/attr-format.datafile.2 [new file with mode: 0644]
data/attr-format.output.1 [new file with mode: 0644]
data/attr-format.output.2 [new file with mode: 0644]
data/attr-format.resolver.1.xml [new file with mode: 0644]
data/attr-format.resolver.2.xml [new file with mode: 0644]
data/attr-mapped.datafile.1 [new file with mode: 0644]
data/attr-mapped.datafile.2 [new file with mode: 0644]
data/attr-mapped.output.1 [new file with mode: 0644]
data/attr-mapped.output.2 [new file with mode: 0644]
data/attr-mapped.resolver.1.xml [new file with mode: 0644]
data/attr-mapped.resolver.2.xml [new file with mode: 0644]
data/attr-regex.datafile.1 [new file with mode: 0644]
data/attr-regex.output.1 [new file with mode: 0644]
data/attr-regex.resolver.1.xml [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/aa/attrresolv/ResolutionPlugInFactory.java
src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/CompositeAttributeDefinition.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/FormattedAttributeDefinition.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/MappedAttributeDefinition.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/RegExAttributeDefinition.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/SimpleAttributeDefinition.java
src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/SimpleBaseAttributeDefinition.java [new file with mode: 0644]
src/schemas/shibboleth-resolver-1.0.xsd
tests/edu/internet2/middleware/shibboleth/aa/attrresolv/AttributesFile.java [new file with mode: 0644]
tests/edu/internet2/middleware/shibboleth/aa/attrresolv/FileConnector.java [new file with mode: 0644]
tests/edu/internet2/middleware/shibboleth/aa/attrresolv/ResolverTests.java

diff --git a/data/attr-composite.datafile.1 b/data/attr-composite.datafile.1
new file mode 100644 (file)
index 0000000..09f235f
--- /dev/null
@@ -0,0 +1,9 @@
+# URLs for various web sites (to be correct, they must be URL Encoded)
+url=http://my.msn.com
+url=http://en.wikipedia.org/wiki/Bangalore
+url=http://www.google.co.in/search?sourceid=navclient&ie=UTF-8&q=%22What+is+Shibboleth%22
+
+# The titles correspond, in order to the URLs listed above
+title=My MSN
+title=Bangalore - From Wikipedia, the free encyclopedia
+title=Google Search: "What is Shibboleth"
diff --git a/data/attr-composite.output.1 b/data/attr-composite.output.1
new file mode 100644 (file)
index 0000000..821c026
--- /dev/null
@@ -0,0 +1,3 @@
+labeledURI=http://my.msn.com My MSN
+labeledURI=http://en.wikipedia.org/wiki/Bangalore Bangalore - From Wikipedia, the free encyclopedia
+labeledURI=http://www.google.co.in/search?sourceid=navclient&ie=UTF-8&q=%22What+is+Shibboleth%22 Google Search: "What is Shibboleth"
diff --git a/data/attr-composite.resolver.1.xml b/data/attr-composite.resolver.1.xml
new file mode 100644 (file)
index 0000000..b749346
--- /dev/null
@@ -0,0 +1,13 @@
+<AttributeResolver xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mace:shibboleth:resolver:1.0" xsi:schemaLocation="urn:mace:shibboleth:resolver:1.0 shibboleth-resolver-1.0.xsd">
+
+  <CompositeAttributeDefinition id="labeledURI"
+    orderedSourceNames="url, title">
+    <DataConnectorDependency requires="file"/>
+  </CompositeAttributeDefinition>
+  
+  <CustomDataConnector id="file"
+    class="edu.internet2.middleware.shibboleth.aa.attrresolv.FileConnector"
+    datafile="data/attr-composite.datafile.1"
+    ordered="true"/>
+
+</AttributeResolver>
diff --git a/data/attr-format.datafile.1 b/data/attr-format.datafile.1
new file mode 100644 (file)
index 0000000..2c961ff
--- /dev/null
@@ -0,0 +1,12 @@
+pdsDateOfBirth=01/01/72
+pdsDateOfBirth=29/02/00
+pdsDateOfBirth=30/03/01
+pdsDateOfBirth=9/4/10
+pdsDateOfBirth=13/5/20
+pdsDateOfBirth=14/6/30
+pdsDateOfBirth=30/07/40
+pdsDateOfBirth=31/08/50
+pdsDateOfBirth=29/09/60
+pdsDateOfBirth=7/010/70
+pdsDateOfBirth=23/11/80
+pdsDateOfBirth=17/12/90
diff --git a/data/attr-format.datafile.2 b/data/attr-format.datafile.2
new file mode 100644 (file)
index 0000000..d492b1e
--- /dev/null
@@ -0,0 +1,12 @@
+gpa=2.0
+gpa=2.99
+gpa=3.0
+gpa=3.01
+gpa=3.45
+gpa=3.5
+gpa=3.50
+gpa=3.51
+gpa=3.8
+gpa=3.88
+gpa=4
+gpa=4.0
diff --git a/data/attr-format.output.1 b/data/attr-format.output.1
new file mode 100644 (file)
index 0000000..dc7e845
--- /dev/null
@@ -0,0 +1,12 @@
+dateOfBirth=Saturday, January 01, 1972
+dateOfBirth=Tuesday, February 29, 2000
+dateOfBirth=Friday, March 30, 2001
+dateOfBirth=Friday, April 09, 2010
+dateOfBirth=Wednesday, May 13, 2020
+dateOfBirth=Saturday, June 14, 1930
+dateOfBirth=Tuesday, July 30, 1940
+dateOfBirth=Thursday, August 31, 1950
+dateOfBirth=Thursday, September 29, 1960
+dateOfBirth=Wednesday, October 07, 1970
+dateOfBirth=Sunday, November 23, 1980
+dateOfBirth=Monday, December 17, 1990
diff --git a/data/attr-format.output.2 b/data/attr-format.output.2
new file mode 100644 (file)
index 0000000..4cf6e4d
--- /dev/null
@@ -0,0 +1,12 @@
+distinction=none
+distinction=none
+distinction=honors
+distinction=honors
+distinction=honors
+distinction=cum laude
+distinction=cum laude
+distinction=cum laude
+distinction=magna cum laude
+distinction=magna cum laude
+distinction=president gold medal
+distinction=president gold medal
diff --git a/data/attr-format.resolver.1.xml b/data/attr-format.resolver.1.xml
new file mode 100644 (file)
index 0000000..836a512
--- /dev/null
@@ -0,0 +1,15 @@
+<AttributeResolver xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mace:shibboleth:resolver:1.0" xsi:schemaLocation="urn:mace:shibboleth:resolver:1.0 shibboleth-resolver-1.0.xsd">
+
+  <!-- This example illustrates simple conversion from one date format to another -->
+  <FormattedAttributeDefinition id="dateOfBirth" sourceName="pdsDateOfBirth">
+       <DataConnectorDependency requires="file"/>
+       <Source format="java.text.SimpleDateFormat" pattern="dd/MM/yy"/>
+    <Target format="java.text.SimpleDateFormat" pattern="EEEEE, MMMMM dd, yyyy"/>
+  </FormattedAttributeDefinition>
+
+  <CustomDataConnector id="file"
+    class="edu.internet2.middleware.shibboleth.aa.attrresolv.FileConnector"
+    datafile="data/attr-format.datafile.1"
+    ordered="true"/>
+
+</AttributeResolver>
diff --git a/data/attr-format.resolver.2.xml b/data/attr-format.resolver.2.xml
new file mode 100644 (file)
index 0000000..632adc0
--- /dev/null
@@ -0,0 +1,16 @@
+<AttributeResolver xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mace:shibboleth:resolver:1.0" xsi:schemaLocation="urn:mace:shibboleth:resolver:1.0 shibboleth-resolver-1.0.xsd">
+
+  <!-- A fictious example of how GPA can be used to assign a distinction -->
+       <FormattedAttributeDefinition id="distinction" sourceName="gpa">
+               <DataConnectorDependency requires="file"/>
+               <Source format="java.text.DecimalFormat" pattern="0.00"/>
+               <Target format="java.text.ChoiceFormat" 
+                       pattern="2&lt;none|2.99&lt;honors|3.49&lt;cum laude|3.79&lt;magna cum laude|4#president gold medal"/>
+       </FormattedAttributeDefinition>
+  
+  <CustomDataConnector id="file"
+    class="edu.internet2.middleware.shibboleth.aa.attrresolv.FileConnector"
+    datafile="data/attr-format.datafile.2"
+    ordered="true"/>
+
+</AttributeResolver>
diff --git a/data/attr-mapped.datafile.1 b/data/attr-mapped.datafile.1
new file mode 100644 (file)
index 0000000..2dff6f0
--- /dev/null
@@ -0,0 +1,2 @@
+pdsRole=AccountAdmin
+pdsRole=affiliate
diff --git a/data/attr-mapped.datafile.2 b/data/attr-mapped.datafile.2
new file mode 100644 (file)
index 0000000..b9c12f5
--- /dev/null
@@ -0,0 +1,2 @@
+role=Faculty
+role=Executive
diff --git a/data/attr-mapped.output.1 b/data/attr-mapped.output.1
new file mode 100644 (file)
index 0000000..0de7bca
--- /dev/null
@@ -0,0 +1,4 @@
+eduPersonAffiliation=staff
+eduPersonAffiliation=member
+eduPersonAffiliation=affiliate
+
diff --git a/data/attr-mapped.output.2 b/data/attr-mapped.output.2
new file mode 100644 (file)
index 0000000..136c210
--- /dev/null
@@ -0,0 +1,3 @@
+eduPersonEntitlement=urn:edu:utah:wasatch:common
+eduPersonEntitlement=urn:edu:utah:wasatch:elearning
+
diff --git a/data/attr-mapped.resolver.1.xml b/data/attr-mapped.resolver.1.xml
new file mode 100644 (file)
index 0000000..9d69f11
--- /dev/null
@@ -0,0 +1,19 @@
+<AttributeResolver xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mace:shibboleth:resolver:1.0" xsi:schemaLocation="urn:mace:shibboleth:resolver:1.0 shibboleth-resolver-1.0.xsd">
+  
+  <MappedAttributeDefinition id="eduPersonAffiliation" sourceName="pdsRole"
+               ignoreCase="true" regex="true">
+    <DataConnectorDependency requires="file"/>
+    <ValueMap keyset="guest, prospect[a-z ]*, friends, affiliate"                      value="affiliate"/>
+    <ValueMap keyset="alum, alumni"                                                    value="alum"/>
+    <ValueMap keyset="employee"                                                        value="employee"/>
+    <ValueMap keyset="faculty"                                                                 value="faculty"/>
+    <ValueMap keyset="admin[a-z ]*, [a-z ]*admin, staff"                               value="staff"/>
+    <ValueMap keyset="student"                                                                 value="student"/>
+    <ValueMap keyset="student, faculty, admin[a-z ]*, [a-z ]*admin, employee, member"  value="member"/>
+  </MappedAttributeDefinition>
+
+  <CustomDataConnector id="file"
+    class="edu.internet2.middleware.shibboleth.aa.attrresolv.FileConnector"
+    datafile="data/attr-mapped.datafile.1"/>
+
+</AttributeResolver>
diff --git a/data/attr-mapped.resolver.2.xml b/data/attr-mapped.resolver.2.xml
new file mode 100644 (file)
index 0000000..75e7fcd
--- /dev/null
@@ -0,0 +1,22 @@
+<AttributeResolver xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mace:shibboleth:resolver:1.0" xsi:schemaLocation="urn:mace:shibboleth:resolver:1.0 shibboleth-resolver-1.0.xsd">
+  
+  <!-- A static way to assign entitlements by role -->
+  <MappedAttributeDefinition id="eduPersonEntitlement" sourceName="role"
+    ignoreCase="true"
+    regex="true"
+    defaultValue="urn:edu:utah:wasatch:common">
+
+    <DataConnectorDependency requires="file"/>
+    <ValueMap keyset="guest, prospect[a-z ]*, friends"                 value="urn:edu:utah:wasatch:public"/>
+    <ValueMap keyset="alum, alumni"                                    value="urn:edu:utah:wasatch:attic"/>
+    <ValueMap keyset="employee"                                                value="urn:edu:utah:wasatch:payroll"/>
+    <ValueMap keyset="faculty"                                         value="urn:edu:utah:wasatch:elearning"/>
+    <ValueMap keyset="admin[a-z ]*, [a-z ]*admin"                      value="urn:edu:utah:wasatch:imanage"/>
+    <ValueMap keyset="student"                                                 value="urn:edu:utah:wasatch:library"/>
+  </MappedAttributeDefinition>
+
+  <CustomDataConnector id="file"
+    class="edu.internet2.middleware.shibboleth.aa.attrresolv.FileConnector"
+    datafile="data/attr-mapped.datafile.2"/>
+
+</AttributeResolver>
diff --git a/data/attr-regex.datafile.1 b/data/attr-regex.datafile.1
new file mode 100644 (file)
index 0000000..e3f9200
--- /dev/null
@@ -0,0 +1,37 @@
+dn=cn=1010.Fall99, ou=Courses,o=sct.com, o=cp
+dn=CN =1999F,ou=Terms, o=sct.com, o=cp
+dn=cN= AccountAdmin,ou= AccessGroups,o=sct.com, o=cp
+dn=Cn = Accounting Managers,ou=groups, o=cp
+dn=cn = Alumni, cn=AccessGroups,o=sct.com, o=cp
+dn=cn=Attribute Authority , ou=Shibboleth, ou = Services, o=sct.com, o=cp
+dn=cn=CMSAccess , ou=Permissions,o=sct.com, o=cp
+dn=cn=Computer Science , ou=Department,o=sct.com, o=cp
+dn=cn=Computer Science, ou=Major,o=sct.com, o=cp
+dn=cn=ContentManagementService,ou=Services,o=sct.com,o=cp
+dn=cn=DefaultCampusPipelineSystemTerm,ou=Terms,o=sct.com, o=cp
+dn=cn=DevelopmentOfficer,ou=AccessGroups,o=sct.com, o=cp
+dn=cn=Directory Administrators, o=cp
+dn=cn=Portal:1:Subscribe:Channel:1,ou=Permissions,o=sct.com, o=cp
+dn=cn=colors,ou=Location,ou=Content,o=sct.com, o=cp
+dn=o=sct.com, o=cp
+dn=ou=AccessGroups,o=sct.com, o=cp
+dn=pdsHtmlID=2216312240658309,cn=Channel,ou=Content,o=sct.com, o=cp
+dn=uid=admin,ou=People,o=sct.com, o=cp
+dn=UID=Admin, cn=People,o=sct.com, o=cp
+
+# This is a multi-valued RDN 
+dn=cn=Vishal Goenka+uid=vgoenka,ou=People,o=sct.com
+
+# Special Characters in RDN
+dn=uid=Portal\+Channel,o=abc.com
+dn=CN=Semi-Colon\;;,o=abc.com
+dn= cn = \#34Escaped\+Hash\, Include , o=sct.com
+dn=uid="Unescaped Quotes", o=ignore
+dn=cn=\"Escaped Quotes\", o=include
+dn=cn=Hello\\World, o=include
+dn=cn=SunGard SCT\, Inc. , cn=SCT, o=com
+dn=cn=One\<Two&Three\>Four, uid=Arithmetic,o=cp
+dn=cn=One\<Two&Three>Four, o=cp
+
+# Semicolon as component separator
+dn=cn=Barbara\=Jones ; ou=Members, o=cp
diff --git a/data/attr-regex.output.1 b/data/attr-regex.output.1
new file mode 100644 (file)
index 0000000..f5a0377
--- /dev/null
@@ -0,0 +1,34 @@
+cn=1010.Fall99
+cn=1999F
+cn=AccountAdmin
+cn=Accounting Managers
+cn=Alumni
+cn=Attribute Authority
+cn=CMSAccess
+cn=Computer Science
+# Duplicates are removed
+# cn=Computer Science
+cn=ContentManagementService
+cn=DefaultCampusPipelineSystemTerm
+cn=DevelopmentOfficer
+cn=Directory Administrators
+cn=Portal:1:Subscribe:Channel:1
+cn=colors
+uid=admin
+
+# Multi-valued RDNs are handled this way ...
+cn=Vishal Goenka
+uid=vgoenka
+
+uid=Portal+Channel
+cn=Semi-Colon;
+cn=#34Escaped+Hash, Include
+cn="Escaped Quotes"
+cn=Hello\World
+cn=SunGard SCT, Inc.
+cn=One<Two&Three>Four
+uid=Arithmetic
+cn=One<Two&Three
+# Semicolon as Separator
+cn=Barbara=Jones
+
diff --git a/data/attr-regex.resolver.1.xml b/data/attr-regex.resolver.1.xml
new file mode 100644 (file)
index 0000000..0287f65
--- /dev/null
@@ -0,0 +1,35 @@
+<AttributeResolver xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mace:shibboleth:resolver:1.0" xsi:schemaLocation="urn:mace:shibboleth:resolver:1.0 shibboleth-resolver-1.0.xsd">
+
+       <RegExAttributeDefinition id="cn" regex="[\\](.)" replacement="$1" partialMatch="true">
+               <AttributeDependency requires="_cn_"/>
+       </RegExAttributeDefinition>
+
+    <!-- Extract cn, but only if CN is the RDN. Ignore Case -->
+    <RegExAttributeDefinition id="_cn_" sourceName="dn"
+      regex='(^cn|[^\w]*cn) *= *(( ?([^, &lt;&gt;+=;#"\\]|[\\][, &lt;&gt;+=;#"\\]))+).*'
+      replacement="$2"
+      ignoreCase="true">
+      <DataConnectorDependency requires="file"/>
+    </RegExAttributeDefinition>
+
+    <!-- Extract uid, even if it is embedded inside the dn. Retrieve the first value of uid from the DN. Case sensitive,
+    since ignoreCase defaults to false -->
+    <RegExAttributeDefinition id="uid" 
+      regex="[\\](.)"
+      replacement="$1"
+      partialMatch="true">
+      <AttributeDependency requires="_uid_"/>
+    </RegExAttributeDefinition>
+
+    <RegExAttributeDefinition id="_uid_" sourceName="dn"
+      regex='(^uid|.*[^\w]uid) *= *(( ?([^, &lt;&gt;+=;#"\\]|[\\][, &lt;&gt;+=;#"\\]))+).*'
+      replacement="$2">
+      <DataConnectorDependency requires="file"/>
+    </RegExAttributeDefinition>
+
+    <CustomDataConnector id="file"
+      class="edu.internet2.middleware.shibboleth.aa.attrresolv.FileConnector"
+      datafile="data/attr-regex.datafile.1"
+      ordered="true"/>
+
+</AttributeResolver>
\ No newline at end of file
index 398c701..271bade 100644 (file)
@@ -1,50 +1,26 @@
-/* 
- * The Shibboleth License, Version 1. 
- * Copyright (c) 2002 
- * University Corporation for Advanced Internet Development, Inc. 
- * All rights reserved
- * 
- * 
- * Redistribution and use in source and binary forms, with or without 
- * modification, are permitted provided that the following conditions are met:
- * 
- * Redistributions of source code must retain the above copyright notice, this 
- * list of conditions and the following disclaimer.
- * 
- * Redistributions in binary form must reproduce the above copyright notice, 
- * this list of conditions and the following disclaimer in the documentation 
- * and/or other materials provided with the distribution, if any, must include 
- * the following acknowledgment: "This product includes software developed by 
- * the University Corporation for Advanced Internet Development 
- * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement 
- * may appear in the software itself, if and wherever such third-party 
- * acknowledgments normally appear.
- * 
- * Neither the name of Shibboleth nor the names of its contributors, nor 
- * Internet2, nor the University Corporation for Advanced Internet Development, 
- * Inc., nor UCAID may be used to endorse or promote products derived from this 
- * software without specific prior written permission. For written permission, 
- * please contact shibboleth@shibboleth.org
- * 
- * Products derived from this software may not be called Shibboleth, Internet2, 
- * UCAID, or the University Corporation for Advanced Internet Development, nor 
- * may Shibboleth appear in their name, without prior written permission of the 
- * University Corporation for Advanced Internet Development.
- * 
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
- * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
- * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK 
- * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. 
- * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY 
- * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, 
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+/*
+ * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
+ * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
+ * provided that the following conditions are met: Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution, if any, must include the following acknowledgment: "This product includes software
+ * developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu>Internet2 Project.
+ * Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
+ * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor
+ * the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
+ * products derived from this software without specific prior written permission. For written permission, please contact
+ * shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the
+ * University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name, without prior
+ * written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS PROVIDED BY THE
+ * COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE
+ * DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO
+ * EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC.
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 package edu.internet2.middleware.shibboleth.aa.attrresolv;
  */
 
 package edu.internet2.middleware.shibboleth.aa.attrresolv;
@@ -52,18 +28,21 @@ package edu.internet2.middleware.shibboleth.aa.attrresolv;
 import org.apache.log4j.Logger;
 import org.w3c.dom.Element;
 
 import org.apache.log4j.Logger;
 import org.w3c.dom.Element;
 
+import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.CompositeAttributeDefinition;
 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.CustomAttributeDefinition;
 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.CustomDataConnector;
 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.CustomAttributeDefinition;
 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.CustomDataConnector;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.FormattedAttributeDefinition;
 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.JDBCDataConnector;
 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.JNDIDirectoryDataConnector;
 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.JDBCDataConnector;
 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.JNDIDirectoryDataConnector;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.MappedAttributeDefinition;
 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.PersistentIDAttributeDefinition;
 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.PersistentIDAttributeDefinition;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.RegExAttributeDefinition;
 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.SimpleAttributeDefinition;
 
 /**
  * Factory that instanciates Resolution PlugIns based on Resolver configuration elements.
  * 
  * @author Walter Hoehn (wassa@columbia.edu)
 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.SimpleAttributeDefinition;
 
 /**
  * Factory that instanciates Resolution PlugIns based on Resolver configuration elements.
  * 
  * @author Walter Hoehn (wassa@columbia.edu)
- *
  */
 
 public class ResolutionPlugInFactory {
  */
 
 public class ResolutionPlugInFactory {
@@ -72,29 +51,25 @@ public class ResolutionPlugInFactory {
 
        public static ResolutionPlugIn createPlugIn(Element e) throws AttributeResolverException {
 
 
        public static ResolutionPlugIn createPlugIn(Element e) throws AttributeResolverException {
 
-               if (e.getTagName().equals("CustomDataConnector")) {
-                       return new CustomDataConnector(e);
-               }
+               if (e.getTagName().equals("CustomDataConnector")) { return new CustomDataConnector(e); }
+
+               if (e.getTagName().equals("CustomAttributeDefinition")) { return new CustomAttributeDefinition(e); }
+
+               if (e.getTagName().equals("SimpleAttributeDefinition")) { return new SimpleAttributeDefinition(e); }
+
+               if (e.getTagName().equals("PersistentIDAttributeDefinition")) { return new PersistentIDAttributeDefinition(e); }
+
+               if (e.getTagName().equals("JNDIDirectoryDataConnector")) { return new JNDIDirectoryDataConnector(e); }
 
 
-               if (e.getTagName().equals("CustomAttributeDefinition")) {
-                       return new CustomAttributeDefinition(e);
-               }
+               if (e.getTagName().equals("JDBCDataConnector")) { return new JDBCDataConnector(e); }
 
 
-               if (e.getTagName().equals("SimpleAttributeDefinition")) {
-                       return new SimpleAttributeDefinition(e);
-               }
+               if (e.getTagName().equals("RegExAttributeDefinition")) { return new RegExAttributeDefinition(e); }
 
 
-               if (e.getTagName().equals("PersistentIDAttributeDefinition")) {
-                       return new PersistentIDAttributeDefinition(e);
-               }
+               if (e.getTagName().equals("FormattedAttributeDefinition")) { return new FormattedAttributeDefinition(e); }
 
 
-               if (e.getTagName().equals("JNDIDirectoryDataConnector")) {
-                       return new JNDIDirectoryDataConnector(e);
-               }
+               if (e.getTagName().equals("CompositeAttributeDefinition")) { return new CompositeAttributeDefinition(e); }
 
 
-               if (e.getTagName().equals("JDBCDataConnector")) {
-                       return new JDBCDataConnector(e);
-               }
+               if (e.getTagName().equals("MappedAttributeDefinition")) { return new MappedAttributeDefinition(e); }
 
                log.error("Unrecognized PlugIn type: " + e.getTagName());
                throw new AttributeResolverException("Failed to initialize PlugIn.");
 
                log.error("Unrecognized PlugIn type: " + e.getTagName());
                throw new AttributeResolverException("Failed to initialize PlugIn.");
diff --git a/src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/CompositeAttributeDefinition.java b/src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/CompositeAttributeDefinition.java
new file mode 100644 (file)
index 0000000..9ba49e8
--- /dev/null
@@ -0,0 +1,268 @@
+/*
+ * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
+ * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
+ * provided that the following conditions are met: Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution, if any, must include the following acknowledgment: "This product includes software
+ * developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu>Internet2 Project.
+ * Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
+ * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor
+ * the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
+ * products derived from this software without specific prior written permission. For written permission, please contact
+ * shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the
+ * University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name, without prior
+ * written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS PROVIDED BY THE
+ * COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE
+ * DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO
+ * EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC.
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Contributed by SungGard SCT.
+ */
+
+package edu.internet2.middleware.shibboleth.aa.attrresolv.provider;
+
+import java.security.Principal;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+
+import org.apache.log4j.Logger;
+import org.w3c.dom.Element;
+
+import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeDefinitionPlugIn;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.Dependencies;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolutionPlugInException;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolverAttribute;
+
+/**
+ * The CompositeAttributeDefinition allows composing a single attribute from multiple attributes. It is particularly
+ * useful when values from several columns of a DataBase must be 'concatenated' to form a single composite attribute. To
+ * ensure that the results are same for a given set of source values, multi-valued source attributes must be ordered and
+ * each must have the same number of values. This is true for the attribute values read using the JDBCDataConnector, but
+ * not true with attributes read using the JNDIDirectoryDataConnector or the LDAPDirectoryDataConnector. Hence,
+ * CompositeAttributeDefinition is only currently meaningful for attributes read from an RDB using the
+ * JDBCDataConnector. The specification of this attribute definition is simple. You specify which source attributes to
+ * compose and the format for composing them using the notation of java.text.MessageFormat. The format defaults to a
+ * space separated concatenation of all source attributes. One use case is in the construction of a labeledURI attribute
+ * from two attributes, the 'URL' and the 'URL_Title' that may appear as two columns in a DataBase. As per the
+ * definition of labeledURI, it is essentially a 'space' separated concatenation of URL followed by the title. Since URL
+ * itself should not contain any spaces (assuming it is properly encoded, converting spaces to +), where the URL ends
+ * and the title begins is unambiguous. The specification fpr such a composite attribute is simply: format="{0} {1}"
+ * orderedSourceNames="URL, URL_Title" The format definition in the above example is optional, since that is infact the
+ * default if not specified. Another example of usage of this attribute is as follows: format="{0} ({1})"
+ * orderedSourceNames="Group_Name, Group_Title" Notice that in this example, we are composing the name of a Group and
+ * the descriptive title of the group in parenthesis using the format to create a single attribute from two attributes.
+ * 
+ * @author <a href="mailto:vgoenka@sungardsct.com">Vishal Goenka </a>
+ */
+
+public class CompositeAttributeDefinition extends SimpleBaseAttributeDefinition implements AttributeDefinitionPlugIn {
+
+       private static Logger log = Logger.getLogger(CompositeAttributeDefinition.class.getName());
+
+       // The formatter used to compose from all Source Attributes
+       private MessageFormat sourceFormat;
+
+       // Names of source attributes to compose the target from, in ordered list
+       private String[] sourceNames;
+
+       // Names of source attributes in a Set for convenience of checking membership
+       private Set sourceNamesSet;
+
+       // Number of values that each source attribute has (must be same for all attributes)
+       private int valueCount = -1;
+
+       public CompositeAttributeDefinition(Element e) throws ResolutionPlugInException {
+
+               super(e);
+
+               try {
+                       // Since there are more than one source objects in an ordered list, one sourceName doesn't make sense. In
+                       // this
+                       // respect, it differs from other attribute definition
+                       if (e.hasAttribute("sourceName"))
+                               throw new ResolutionPlugInException(
+                                               "sourceName is not an allowed attribute for CompositeAttributeDefinition (" + getId() + ")");
+
+                       String orderedSourceNames = e.getAttribute("orderedSourceNames");
+                       if ((orderedSourceNames == null) || ("".equals(orderedSourceNames)))
+                               throw new ResolutionPlugInException(
+                                               "orderedSourceNames is a required attribute for CompositeAttributeDefinition (" + getId() + ")");
+
+                       // We assume space or comma as separators
+                       StringTokenizer st = new StringTokenizer(orderedSourceNames, " ,");
+                       ArrayList sourceNamesList = new ArrayList();
+                       while (st.hasMoreTokens()) {
+                               String token = st.nextToken().trim();
+                               if (token.length() > 0) sourceNamesList.add(token);
+                       }
+                       sourceNamesSet = new HashSet();
+                       sourceNamesSet.addAll(sourceNamesList);
+                       sourceNames = (String[]) sourceNamesList.toArray(new String[0]);
+
+                       String format = e.getAttribute("format");
+                       // default format is essentially all ordered attribute values separated by a space
+                       if ((format == null) || ("".equals(format))) {
+                               StringBuffer defaultFormat = new StringBuffer();
+                               for (int i = 0; i < sourceNames.length; i++) {
+                                       defaultFormat.append("{").append(i).append("}");
+                                       if (i < sourceNames.length - 1) defaultFormat.append(" ");
+                               }
+                               format = defaultFormat.toString();
+                       }
+                       sourceFormat = new MessageFormat(format);
+               } catch (ResolutionPlugInException ex) {
+                       // To ensure that exceptions thrown in the constructor are logged!
+                       log.error(ex.getMessage());
+                       throw ex;
+               } catch (RuntimeException ex) {
+                       // To ensure that exceptions thrown in the constructor are logged!
+                       log.error(ex.getMessage());
+                       throw ex;
+               }
+       }
+
+       /**
+        * Get ordered attribute values for all source attributes from the dependent data connectors. The values of all
+        * multi-valued attribute MUST be ordered and MUST be of same size or else the results can be unpredictable.
+        */
+       private void addAttributesFromConnectors(Dependencies depends, Attributes sourceAttrs)
+                       throws ResolutionPlugInException {
+
+               Iterator connectorDependIt = connectorDependencyIds.iterator();
+               while (connectorDependIt.hasNext()) {
+                       Attributes attrs = depends.getConnectorResolution((String) connectorDependIt.next());
+                       if (attrs != null) {
+                               for (int i = 0; i < sourceNames.length; i++) {
+                                       Attribute attr = attrs.get(sourceNames[i]);
+                                       if (attr != null) {
+                                               int size = attr.size();
+                                               if (!attr.isOrdered() && (size > 1)) { throw new ResolutionPlugInException(
+                                                               "Multi-valued attribute (" + attr.getID()
+                                                                               + ") MUST be ordered for CompositeAttributeDefinition (" + getId() + ")"); }
+                                               if (valueCount == -1) {
+                                                       valueCount = size; // initialize valueCount
+                                               } else if (valueCount != size) { throw new ResolutionPlugInException("Multi-valued attribute ("
+                                                               + attr.getID() + ") has different number of values (" + size
+                                                               + ") than other attribute(s) that have (" + valueCount + ") values. "
+                                                               + "All attributes must have same number of values for CompositeAttributeDefinition ("
+                                                               + getId() + ")"); }
+                                               if (sourceAttrs.put(attr) != null) { throw new ResolutionPlugInException("Attribute ("
+                                                               + attr.getID() + ") occured more than once in the dependency chain for (" + getId()
+                                                               + ") and I don't know which one to pick"); }
+                                       }
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Get ordered attribute values for all source attributes from the dependent attributes. The values of all
+        * multi-valued attribute MUST be ordered and MUST be of same size or else the results can be unpredictable.
+        */
+       private void addAttributesFromAttributeDependencies(Dependencies depends, Attributes sourceAttrs)
+                       throws ResolutionPlugInException {
+
+               Iterator attrDependIt = attributeDependencyIds.iterator();
+               while (attrDependIt.hasNext()) {
+                       ResolverAttribute attribute = depends.getAttributeResolution((String) attrDependIt.next());
+                       if (attribute != null) {
+                               if (sourceNamesSet.contains(attribute.getName())) {
+                                       BasicAttribute attr = new BasicAttribute(attribute.getName(), true);
+                                       int size = 0;
+                                       for (Iterator iterator = attribute.getValues(); iterator.hasNext();) {
+                                               attr.add(size++, iterator.next());
+                                       }
+                                       if (valueCount == -1) {
+                                               valueCount = size; // initialize valueCount
+                                       } else if (valueCount != size) { throw new ResolutionPlugInException("Multi-valued attribute ("
+                                                       + attr.getID() + ") has different number of values (" + size
+                                                       + ") than other attribute(s) that have (" + valueCount + ") values. "
+                                                       + "All attributes must have same number of values for CompositeAttributeDefinition ("
+                                                       + getId() + ")"); }
+                                       if (sourceAttrs.put(attr) != null) { throw new ResolutionPlugInException("Attribute ("
+                                                       + attr.getID() + ") occured more than once in the dependency chain for (" + getId()
+                                                       + ") and I don't know which one to pick"); }
+                               } else {
+                                       log
+                                                       .warn("Attribute Dependency ("
+                                                                       + attribute.getName()
+                                                                       + ") is not listed in the orderedSourceNames attribute for the CustomAttributeDefinition for ("
+                                                                       + getId() + ")");
+                               }
+                       }
+               }
+       }
+
+       /**
+        * @see edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeDefinitionPlugIn#resolve(
+        *      edu.internet2.middleware.shibboleth.aa.attrresolv.ArpAttribute, java.security.Principal, java.lang.String,
+        *      edu.internet2.middleware.shibboleth.aa.attrresolv.Dependencies)
+        */
+       public void resolve(ResolverAttribute attribute, Principal principal, String requester, Dependencies depends)
+                       throws ResolutionPlugInException {
+
+               // Collect attribute values from dependencies
+               BasicAttributes attributes = new BasicAttributes();
+               addAttributesFromConnectors(depends, attributes);
+               addAttributesFromAttributeDependencies(depends, attributes);
+
+               // If we got this far, all attributes are ordered and have 'valueCount' number of values
+               for (int i = 0; i < valueCount; i++) {
+                       // put values in an array so we can use the formatter for creating the composite value
+                       Object[] values = new Object[sourceNames.length];
+                       try {
+                               for (int j = 0; j < sourceNames.length; j++) {
+                                       Attribute attr = attributes.get(sourceNames[j]);
+                                       if (attr == null)
+                                               throw new ResolutionPlugInException("No value found for attribute (" + sourceNames[j]
+                                                               + ") during resolution of (" + getId() + ")");
+                                       // get the ordered (i'th) value of the attribute
+                                       values[j] = attr.get(i);
+                               }
+                               attribute.addValue(sourceFormat.format(values));
+                       } catch (ResolutionPlugInException e) {
+                               // Simply rethrow ...
+                               throw e;
+                       } catch (Exception e) {
+                               StringBuffer err = new StringBuffer();
+                               err.append("Error creating composite attribute [");
+                               if (values != null) {
+                                       for (int ii = 0; ii < values.length; ii++) {
+                                               if (values[ii] != null) err.append(values[ii].toString()).append(", ");
+                                               else err.append("null ");
+                                       }
+                               } else err.append(" null ");
+                               err.append("] using format ").append(sourceFormat.toPattern()).append(" for (").append(getId()).append(
+                                               "): ").append(e.getMessage());
+                               log.error(err.toString());
+                               throw new ResolutionPlugInException(err.toString());
+                       }
+               }
+               if (lifeTime != -1) {
+                       attribute.setLifetime(lifeTime);
+               }
+
+               if (valueHandler != null) {
+                       attribute.registerValueHandler(valueHandler);
+               }
+               attribute.setResolved();
+       }
+
+}
diff --git a/src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/FormattedAttributeDefinition.java b/src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/FormattedAttributeDefinition.java
new file mode 100644 (file)
index 0000000..bd294c0
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
+ * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
+ * provided that the following conditions are met: Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution, if any, must include the following acknowledgment: "This product includes software
+ * developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu>Internet2 Project.
+ * Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
+ * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor
+ * the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
+ * products derived from this software without specific prior written permission. For written permission, please contact
+ * shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the
+ * University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name, without prior
+ * written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS PROVIDED BY THE
+ * COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE
+ * DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO
+ * EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC.
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Contributed by SungGard SCT.
+ */
+
+package edu.internet2.middleware.shibboleth.aa.attrresolv.provider;
+
+import java.lang.reflect.Constructor;
+import java.security.Principal;
+import java.text.Format;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeDefinitionPlugIn;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.Dependencies;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolutionPlugInException;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolverAttribute;
+
+/**
+ * The FormattedAttributeDefinition allows attribute values to be formatted using any custom formatter, such as the
+ * java.text.MessageFormat, java.text.DateFormat or java.text.NumberFormat etc. It allows the source attribute to be
+ * parsed using a specified formatter and the target to formatted using another formatter. Since the formatters for
+ * source and target are both passed in as arguments along with the pattern string, a high degree of customization is
+ * allowed. A value handler may also be specified, if needed. The value handler will see the 'target' value after the
+ * target formatter has completed the value transformation. The 'format' attribute of the Source and Target elements
+ * specify the class name of the formatter and MUST be a subclass of java.text.Format. The
+ * <code>sourceFormat.parseObject(sourceValue)</code> method is used to convert the source attribute to an Object
+ * (say, obj), which is then converted back to a String using the <code>targetFormat.format( obj )</code> method.
+ * 
+ * @author <a href="mailto:vgoenka@sungardsct.com">Vishal Goenka </a>
+ */
+
+public class FormattedAttributeDefinition extends SimpleBaseAttributeDefinition implements AttributeDefinitionPlugIn {
+
+       private static Logger log = Logger.getLogger(FormattedAttributeDefinition.class.getName());
+
+       // The format of Source Attribute
+       private Format sourceFormat;
+
+       // The format of Target Attribute
+       private Format targetFormat;
+
+       // if source and target formatters are same should we skip formatting? In a few cases, the formatter may still
+       // produce
+       // a different result so one may still want the formatting, even though the source and target formatters are
+       // identical.
+       private boolean skipFormatting = false;
+
+       public FormattedAttributeDefinition(Element e) throws ResolutionPlugInException {
+
+               super(e);
+               NodeList sources = e.getElementsByTagName("Source");
+               NodeList targets = e.getElementsByTagName("Target");
+
+               if ((sources == null) || (sources.getLength() != 1) || (targets == null) || (targets.getLength() != 1)) {
+                       log
+                                       .error("There MUST be exactly 1 'Source' and 1 'Target' definition for a FormattedAttributeDefinition for ("
+                                                       + getId() + ")");
+                       throw new ResolutionPlugInException(
+                                       "There MUST be exactly 1 'Source' and 1 'Target' definition for a FormattedAttributeDefinition for ("
+                                                       + getId() + ")");
+               }
+               Element source = (Element) sources.item(0);
+               Element target = (Element) targets.item(0);
+
+               sourceFormat = createFormat(source);
+               targetFormat = createFormat(target);
+
+               if (sourceFormat.equals(targetFormat)) {
+                       String skipIfSameFormat = e.getAttribute("skipIfSameFormat");
+                       skipFormatting = Boolean.valueOf(skipIfSameFormat).booleanValue();
+                       if (!skipFormatting) {
+                               log.warn("Source and Target formats are identical for (" + getId()
+                                               + "). Set 'skipIfSameFormat=true' on the CustomAttributeDefinition element to skip.");
+                       } else {
+                               log.debug("Source and Target formats are identical for (" + getId()
+                                               + "). Formatting will be skipped since 'skipIfSameFormat=true'");
+                       }
+               }
+       }
+
+       /**
+        * @see edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeDefinitionPlugIn#resolve(
+        *      edu.internet2.middleware.shibboleth.aa.attrresolv.ArpAttribute, java.security.Principal, java.lang.String,
+        *      edu.internet2.middleware.shibboleth.aa.attrresolv.Dependencies)
+        */
+       public void resolve(ResolverAttribute attribute, Principal principal, String requester, Dependencies depends)
+                       throws ResolutionPlugInException {
+
+               // Resolve all dependencies to arrive at the source values (unformatted)
+               Collection results = resolveDependencies(attribute, principal, requester, depends);
+
+               Iterator resultsIt = results.iterator();
+
+               while (resultsIt.hasNext()) {
+                       String value = getString(resultsIt.next());
+                       if (skipFormatting) attribute.addValue(value);
+                       else {
+                               try {
+                                       Object parsed = sourceFormat.parseObject(value);
+                                       value = targetFormat.format(parsed);
+                                       attribute.addValue(value);
+                               } catch (Exception e) {
+                                       // We simply log an error for values that give errors during formatting rather than abandoning the
+                                       // whole process
+                                       log.error("Attribute value for (" + getId() + ") --> (" + value
+                                                       + ") failed during format conversion with exception: " + e.getMessage());
+                               }
+                       }
+               }
+               attribute.setResolved();
+       }
+
+       /**
+        * Construct the specified formatter, which must be an instance of java.text.Format. It reads the attributes
+        * 'format' and 'pattern' from the specified element, asserts that both are non-null and instantiates the class
+        * specified by 'format' by passing in the 'pattern' to the single String argument constructor.
+        * 
+        * @param element
+        *            the XML element describing the FormatType as defined in the FormattedAttributeDefinition.xsd
+        * @return the initialized formatter, which must be a sub-class of java.text.Format
+        */
+       private Format createFormat(Element element) throws ResolutionPlugInException {
+
+               String elementName = element.getTagName();
+               String format = element.getAttribute("format");
+               String pattern = element.getAttribute("pattern");
+
+               log.debug(getId() + " <" + elementName + " format=\"" + format + "\" pattern=\"" + pattern + "\"/>");
+
+               if ((format == null) || ("".equals(format)) || (pattern == null) || ("".equals(pattern))) {
+                       String error = elementName + " must have 'format' and 'pattern' attributes specified for (" + getId() + ")";
+                       log.error(error);
+                       throw new ResolutionPlugInException(error);
+               }
+               try {
+                       Class formatClass = Class.forName(format);
+                       if (!Format.class.isAssignableFrom(formatClass)) { throw new ResolutionPlugInException("Specified format ("
+                                       + format + ") MUST be a subclass of java.text.Format"); }
+                       Constructor formatCons = formatClass.getConstructor(new Class[]{String.class});
+                       return (Format) formatCons.newInstance(new String[]{pattern});
+               } catch (ClassNotFoundException e) {
+                       String error = "Specified format class (" + format + ") could not be found for (" + getId() + ")";
+                       log.error(error);
+                       throw new ResolutionPlugInException(error);
+               } catch (Exception e) {
+                       String error = "Error creating " + elementName + " formatter for (" + getId() + "). Cause: "
+                                       + e.getMessage();
+                       log.error(error, e);
+                       throw new ResolutionPlugInException(error);
+               }
+       }
+
+}
diff --git a/src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/MappedAttributeDefinition.java b/src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/MappedAttributeDefinition.java
new file mode 100644 (file)
index 0000000..333e9e7
--- /dev/null
@@ -0,0 +1,286 @@
+/*
+ * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
+ * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
+ * provided that the following conditions are met: Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution, if any, must include the following acknowledgment: "This product includes software
+ * developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu>Internet2 Project.
+ * Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
+ * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor
+ * the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
+ * products derived from this software without specific prior written permission. For written permission, please contact
+ * shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the
+ * University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name, without prior
+ * written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS PROVIDED BY THE
+ * COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE
+ * DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO
+ * EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC.
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Contributed by SungGard SCT.
+ */
+
+package edu.internet2.middleware.shibboleth.aa.attrresolv.provider;
+
+import java.security.Principal;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.regex.Pattern;
+
+import org.apache.log4j.Logger;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeDefinitionPlugIn;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.Dependencies;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolutionPlugInException;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolverAttribute;
+
+/**
+ * The MappedAttributeDefinition allows an enumeration of mappings between the source attribute values received from a
+ * data connector and the values that are returned. The enumeration is essentially a many-to-many mapping in the general
+ * case, one-to-one being a very specific but plausible case. The mapping is specified using a series of
+ * &lt;ValueMap&gt; elements, each containing a [key set -> value] element. Thus each &lt;ValueMap&gt; is a many-to-one
+ * mapping, but since the elements in the keyset are allowed to re-appear in a subsequent &lt;ValueMap&gt;, it becomes a
+ * many-to-many mapping. For instance, consider a sample mapping of Luminis Role to an eduPersonAffiliation. Luminis
+ * roles are arbitrary whereas eduPersonAffiliation has a constrained set of values, namely affiliate, alum, employee,
+ * faculty, student, staff and member. A potential mapping may look as follows:
+ * 
+ * <pre>
+ * 
+ *  
+ *        &lt;ValueMap value=&quot;affiliate&quot;  keyset=&quot;guest, prospect[a-z ]*, friends&quot;                        /&gt;
+ *        &lt;ValueMap value=&quot;alum&quot;       keyset=&quot;alum, alumni&quot;                                           /&gt;
+ *        &lt;ValueMap value=&quot;employee&quot;   keyset=&quot;employee&quot;                                               /&gt;
+ *        &lt;ValueMap value=&quot;faculty&quot;    keyset=&quot;faculty&quot;                                                /&gt;
+ *        &lt;ValueMap value=&quot;member&quot;     keyset=&quot;student, faculty, admin[a-z ]*, [a-z ]*admin, employee&quot; /&gt;
+ *        &lt;ValueMap value=&quot;staff&quot;      keyset=&quot;admin[a-z ]*, [a-z ]*admin&quot;                             /&gt;
+ *        &lt;ValueMap value=&quot;student&quot;    keyset=&quot;student&quot;                                                /&gt;
+ *   
+ *  
+ * </pre>
+ * 
+ * This many-to-many mapping will result in a Luminis role of 'student' to imply eduPersonAffiliation values of [member,
+ * student] and a Luminis role of admin to imply eduPersonAffiliation value of [member, staff]. The separator used in
+ * specifying the keyset can be specified in the &lt;ValueMap&gt; itself and defaults to ",". Leading or trailing spaces
+ * in keys are ignored. As illustrated by the above example, the keyset can contain regular expressions against which
+ * the source values of the attributes may be matched to arrive at the value mapping. To allow special characters that
+ * have special significance as regular expressions (such as <code>*</code>) to appear in the keyset, an attribute
+ * 'regex' can be set to false, thus implying that all keys in the keyset should be literally matched. The match can be
+ * specified to be case insensitive by setting 'ignoreCase' attribute to true. Since one attribute value can have
+ * multiple mappings, and the ValueMap elements are unordered, specifing a catch-all mapping, such as: &lt;ValueMap
+ * value="member" keyset="[a-z]*" /&gt; is sure to match every value, irrespective of whether another match was found
+ * for the attribute. To allow such a catch-all specification, an attribute 'defaultValue' can be set to the 'catch-all'
+ * value. If 'defaultValue' is set to a special value of ampersand (&amp;), the original attribute value itself is added
+ * to the attribute. The algorithm for this implementation is the following: We take the [keyset -> value]* mappings
+ * specified in the MappedAttributeDefinition (which is perhaps easier to specify) and reverse it to [key -> value set]*
+ * mappings internally. These mappings are stored as HashMaps, one HashMap for regex keys and another for non-regex
+ * keys. Every attribute value to be resolved is looked up in both these HashMaps and all matching values in the value
+ * set is added in lieu of the attribute value to be resolved. If no mapping is found, we use the defaultValue (if
+ * specified).
+ * 
+ * @author <a href="mailto:vgoenka@sungardsct.com">Vishal Goenka </a>
+ */
+
+public class MappedAttributeDefinition extends SimpleBaseAttributeDefinition implements AttributeDefinitionPlugIn {
+
+       private static Logger log = Logger.getLogger(MappedAttributeDefinition.class.getName());
+
+       // [simple-key -> mapped value set], where simple-key is not a regular expression
+       private HashMap simpleValueMap;
+
+       // [regex-key -> mapped value set], where regex-key is a regular expression
+       private HashMap regexValueMap;
+
+       // Should we ignore case when matching against simple or regex keys
+       private boolean ignoreCase = false;
+
+       // Default value, if no other value mapping is found
+       private String defaultValue;
+
+       // Does the keyset contain regular expressions or simply value strings?
+       private boolean regexExpected = false;
+
+       // A pattern that describes a word (without any white space or special characters) This is used to separate 'simple'
+       // keys from 'regex' keys when the ValueMap is parsed.
+       private Pattern word;
+
+       public MappedAttributeDefinition(Element e) throws ResolutionPlugInException {
+
+               super(e);
+
+               // Does the keyset contain regular expressions or simply value strings?
+               regexExpected = Boolean.valueOf(e.getAttribute("regex")).booleanValue();
+
+               // Is the keyset case sensitive
+               ignoreCase = Boolean.valueOf(e.getAttribute("ignoreCase")).booleanValue();
+
+               defaultValue = e.getAttribute("defaultValue");
+
+               // Initialize maps to contain [keyset -> value] mappings
+               initializeValueMaps();
+
+               NodeList valueMaps = e.getElementsByTagName("ValueMap");
+               int count = valueMaps.getLength();
+               for (int i = 0; i < count; i++) {
+                       Element valueMap = (Element) valueMaps.item(i);
+                       // Parse each ValueMap and initialize the internal data structures
+                       parseValueMap(valueMap);
+               }
+       }
+
+       /**
+        * @see edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeDefinitionPlugIn#resolve(
+        *      edu.internet2.middleware.shibboleth.aa.attrresolv.ArpAttribute, java.security.Principal, java.lang.String,
+        *      edu.internet2.middleware.shibboleth.aa.attrresolv.Dependencies)
+        */
+       public void resolve(ResolverAttribute attribute, Principal principal, String requester, Dependencies depends)
+                       throws ResolutionPlugInException {
+
+               // Resolve all dependencies to arrive at the source values (unformatted)
+               Collection results = resolveDependencies(attribute, principal, requester, depends);
+
+               Iterator resultsIt = results.iterator();
+
+               // Create string for debugging output
+               StringBuffer debugBuffer = new StringBuffer();
+
+               while (resultsIt.hasNext()) {
+                       // Read the source value (prior to mapping)
+                       String valueExactCase = getString(resultsIt.next());
+                       String value = valueExactCase;
+                       boolean mapped = false;
+
+                       if (log.isDebugEnabled()) debugBuffer.append("[").append(value).append(" --> ");
+
+                       // The source is converted to lowercase for matching ... the mapped value is returned in 'exact case'
+                       if (ignoreCase) value = value.toLowerCase();
+
+                       // First check if there are value mappings based on exact-match rather than regex matches
+                       Set simpleMappedValues = (Set) simpleValueMap.get(value);
+                       if (simpleMappedValues != null) {
+                               // Add all mapped values to the attribute
+                               for (Iterator it = simpleMappedValues.iterator(); it.hasNext();) {
+                                       String simpleMappedValue = (String) it.next();
+                                       attribute.addValue(simpleMappedValue);
+                                       mapped = true;
+                                       if (log.isDebugEnabled()) debugBuffer.append(simpleMappedValue).append(", ");
+                               }
+                       }
+
+                       // If we are expecting the keyset to contain regular expressions, the matching process is exhaustive!
+                       try {
+                               if (regexExpected) {
+                                       // Check all entries in the hashmap for a regex match between the source value and the regex-key
+                                       for (Iterator it = regexValueMap.entrySet().iterator(); it.hasNext();) {
+                                               Map.Entry entry = (Map.Entry) it.next();
+                                               Pattern regexKey = (Pattern) entry.getKey();
+                                               if (regexKey.matcher(value).matches()) {
+                                                       // Add all values
+                                                       Set regexMappedValues = (Set) entry.getValue();
+                                                       for (Iterator vit = regexMappedValues.iterator(); vit.hasNext();) {
+                                                               String regexMappedValue = (String) vit.next();
+                                                               attribute.addValue(regexMappedValue);
+                                                               mapped = true;
+                                                               if (log.isDebugEnabled()) debugBuffer.append(regexMappedValue).append(", ");
+                                                       }
+                                               }
+                                       }
+                               }
+                       } catch (Exception e) {
+                               // Any exception during the regex match only skips the attribute value being matched ...
+                               log.error("Attribute value for (" + getId() + ") --> (" + value
+                                               + ") failed during regex processing with exception: " + e.getMessage());
+                       }
+
+                       // Was there was no mapping found for this value?
+                       if (!mapped && (defaultValue != null) && (defaultValue.length() > 0)) {
+                               if (defaultValue.equals("&")) {
+                                       attribute.addValue(valueExactCase);
+                                       if (log.isDebugEnabled()) debugBuffer.append(valueExactCase);
+                               } else {
+                                       attribute.addValue(defaultValue);
+                                       if (log.isDebugEnabled()) debugBuffer.append(defaultValue);
+                               }
+                       }
+
+                       if (log.isDebugEnabled()) debugBuffer.append("] ");
+               }
+               attribute.setResolved();
+               if (log.isDebugEnabled())
+                       log.debug("Attribute values upon mapping for (" + getId() + "): " + debugBuffer.toString());
+       }
+
+       /**
+        * Helper method ... allocates hashmaps etc.
+        */
+       private void initializeValueMaps() {
+
+               simpleValueMap = new HashMap();
+               regexValueMap = new HashMap();
+               word = Pattern.compile("^\\w+$");
+       }
+
+       /**
+        * This method reads the [value --> keyset] and reverses the mapping to [key -> value set]* for easier attribute
+        * resolution. Each key in the keyset is evaluated for whether it is a regex or not, and is stored in the
+        * regexValueMap or simpleValueMap based on whether the key contains any non-word characters.
+        */
+       private void parseValueMap(Element element) throws ResolutionPlugInException {
+
+               String value = element.getAttribute("value");
+               String keyset = element.getAttribute("keyset");
+               String separator = element.getAttribute("separator");
+
+               if ((value == null) || ("".equals(value)) || (keyset == null) || ("".equals(keyset))) {
+                       String error = "value and keyset attributes MUST both be non-empty in the ValueMap element for attribute ("
+                                       + getId() + ")";
+                       log.error(error);
+                       throw new ResolutionPlugInException(error);
+               }
+
+               // The separator for entries in the keyset
+               if ((separator == null) || ("".equals(separator))) separator = ",";
+
+               StringTokenizer st = new StringTokenizer(keyset, separator);
+               while (st.hasMoreTokens()) {
+                       // trim to remove spaces that are used immediately after a separator in the keyset, even when space
+                       // otherwise is
+                       // not a separator
+                       String key = st.nextToken().trim();
+
+                       // If ignoreCase, values will also be converted to lowercase before the match is attempted
+                       if (ignoreCase) key = key.toLowerCase();
+
+                       // Lets assume that the key is a simple String and therefore the valueMap to store the mapping is
+                       // simpleValueMap
+                       Object keyObject = key;
+                       HashMap valueMap = simpleValueMap;
+
+                       // If we are expecting regex and this is one, add it to regex value map, else add it to simpleValueMap
+                       if (regexExpected && !word.matcher(key).matches()) {
+                               keyObject = Pattern.compile(key);
+                               valueMap = regexValueMap;
+                       }
+
+                       HashSet valueSet = (HashSet) valueMap.get(keyObject);
+                       if (valueSet == null) {
+                               valueSet = new HashSet();
+                               valueMap.put(keyObject, valueSet);
+                       }
+                       valueSet.add(value);
+               }
+       }
+}
diff --git a/src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/RegExAttributeDefinition.java b/src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/RegExAttributeDefinition.java
new file mode 100644 (file)
index 0000000..aaeca5d
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
+ * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
+ * provided that the following conditions are met: Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution, if any, must include the following acknowledgment: "This product includes software
+ * developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu>Internet2 Project.
+ * Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
+ * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor
+ * the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
+ * products derived from this software without specific prior written permission. For written permission, please contact
+ * shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the
+ * University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name, without prior
+ * written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS PROVIDED BY THE
+ * COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE
+ * DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO
+ * EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC.
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Contributed by SungGard SCT.
+ */
+
+package edu.internet2.middleware.shibboleth.aa.attrresolv.provider;
+
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.log4j.Logger;
+import org.w3c.dom.Element;
+
+import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeDefinitionPlugIn;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.Dependencies;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolutionPlugInException;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolverAttribute;
+
+/**
+ * The RegExAttributeDefinition allows regular expression based replacements on attribute values, using the regex syntax
+ * allowed by java.util.regex.Pattern. Capturing groups can be specified in the regex string using parenthesis and in
+ * the replacement string using $i, where i = 0-9. Case-insensitive matches can be specified using the 'ignoreCase'
+ * attribute set to true. No other flags allowed by java.util.regex.Pattern are deemed useful and hence not supported
+ * yet, but are easy to add if needed.
+ * 
+ * @author <a href="mailto:vgoenka@sungardsct.com">Vishal Goenka </a>
+ */
+
+/*
+ * An alternate way to implement a regex replacement would be to write a special value handler, pre-configured with the
+ * regex pattern and replacement string. Since ValueHandlers are initialized using the default no-args constructor,
+ * their re-usability based on configuration parameters is limited. The RegExAttributeDefinition therefore builds on the
+ * SimpleAttributeDefinition by allowing specification of regular expression parameters as configuration values instead.
+ * A value handler may also be specified, if needed. The value handler will see the 'formatted' value after the regular
+ * expression substitution.
+ */
+
+public class RegExAttributeDefinition extends SimpleBaseAttributeDefinition implements AttributeDefinitionPlugIn {
+
+       private static Logger log = Logger.getLogger(RegExAttributeDefinition.class.getName());
+
+       // The pattern to match the source attribute value with
+       private Pattern pattern;
+
+       // Unless partialMatch is set to true (defaults to false), the pattern MUST match the full value
+       private boolean partialMatch = false;
+
+       // The replacement string to replace the matched groups in the pattern with, must be non-empty unless partialMatch
+       // is set to true
+       private String replacement;
+
+       public RegExAttributeDefinition(Element e) throws ResolutionPlugInException {
+
+               super(e);
+
+               try {
+                       String regex = e.getAttribute("regex");
+                       if ((regex == null) || ("".equals(regex)))
+                               throw new ResolutionPlugInException("(" + getId()
+                                               + ") 'regex' is a required attribute for RegExAttributeDefinition");
+
+                       partialMatch = Boolean.valueOf(e.getAttribute("partialMatch")).booleanValue();
+
+                       replacement = e.getAttribute("replacement");
+                       if (!partialMatch && ((replacement == null) || ("".equals(replacement))))
+                               throw new ResolutionPlugInException(
+                                               "("
+                                                               + getId()
+                                                               + ") 'replacement' MUST NOT be empty, unless 'partialMatch' is true for RegExAttributeDefinition");
+
+                       int flags = 0;
+                       boolean ignoreCase = Boolean.valueOf(e.getAttribute("ignoreCase")).booleanValue();
+                       if (ignoreCase) flags = Pattern.CASE_INSENSITIVE;
+
+                       pattern = Pattern.compile(regex, flags);
+
+                       if (log.isDebugEnabled())
+                               log.debug("RegEx Pattern = " + pattern.pattern() + ", Replacement = " + replacement + " for ("
+                                               + getId() + ")");
+               } catch (ResolutionPlugInException ex) {
+                       // To ensure that exceptions thrown in the constructor are logged!
+                       log.error(ex.getMessage());
+                       throw ex;
+               }
+       }
+
+       /**
+        * @see edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeDefinitionPlugIn#resolve(
+        *      edu.internet2.middleware.shibboleth.aa.attrresolv.ArpAttribute, java.security.Principal, java.lang.String,
+        *      edu.internet2.middleware.shibboleth.aa.attrresolv.Dependencies)
+        */
+       public void resolve(ResolverAttribute attribute, Principal principal, String requester, Dependencies depends)
+                       throws ResolutionPlugInException {
+
+               // Resolve all dependencies to arrive at the source values (unformatted)
+               Collection results = resolveDependencies(attribute, principal, requester, depends);
+
+               Iterator resultsIt = results.iterator();
+
+               while (resultsIt.hasNext()) {
+                       String value = getString(resultsIt.next());
+                       Matcher m = pattern.matcher(value);
+                       try {
+                               if (partialMatch || m.matches()) attribute.addValue(m.replaceAll(replacement));
+                               else log.debug("Attribute value for (" + getId() + ") --> (" + value + ") did not match regex pattern");
+                       } catch (Exception e) {
+                               // We simply log an error for values that give errors during formatting rather than abandoning the whole
+                               // process
+                               log.error("Attribute value for (" + getId() + ") --> (" + value
+                                               + ") failed during regex processing with exception: " + e.getMessage());
+                       }
+               }
+               attribute.setResolved();
+       }
+}
index f7f4e29..958a20b 100644 (file)
@@ -31,11 +31,6 @@ import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.Set;
 
 import java.util.LinkedHashSet;
 import java.util.Set;
 
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-
 import org.apache.log4j.Logger;
 import org.w3c.dom.Element;
 
 import org.apache.log4j.Logger;
 import org.w3c.dom.Element;
 
@@ -50,7 +45,7 @@ import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolverAttribute;
  * 
  * @author Walter Hoehn (wassa@columbia.edu)
  */
  * 
  * @author Walter Hoehn (wassa@columbia.edu)
  */
-public class SimpleAttributeDefinition extends BaseAttributeDefinition implements AttributeDefinitionPlugIn {
+public class SimpleAttributeDefinition extends SimpleBaseAttributeDefinition implements AttributeDefinitionPlugIn {
 
        private static Logger log = Logger.getLogger(SimpleAttributeDefinition.class.getName());
        private String connectorMapping;
 
        private static Logger log = Logger.getLogger(SimpleAttributeDefinition.class.getName());
        private String connectorMapping;
@@ -67,7 +62,7 @@ public class SimpleAttributeDefinition extends BaseAttributeDefinition implement
 
                super(e);
 
 
                super(e);
 
-               //Parse source name
+               // Parse source name
                String sourceName = e.getAttribute("sourceName");
                if (sourceName == null || sourceName.equals("")) {
                        int index = getId().lastIndexOf("#");
                String sourceName = e.getAttribute("sourceName");
                if (sourceName == null || sourceName.equals("")) {
                        int index = getId().lastIndexOf("#");
@@ -85,7 +80,7 @@ public class SimpleAttributeDefinition extends BaseAttributeDefinition implement
 
                log.debug("Mapping attribute to name (" + connectorMapping + ") in connector.");
 
 
                log.debug("Mapping attribute to name (" + connectorMapping + ") in connector.");
 
-               //Configure smart scoping
+               // Configure smart scoping
                String smartScopingSpec = e.getAttribute("smartScope");
                if (smartScopingSpec != null && !smartScopingSpec.equals("")) {
                        smartScope = smartScopingSpec;
                String smartScopingSpec = e.getAttribute("smartScope");
                if (smartScopingSpec != null && !smartScopingSpec.equals("")) {
                        smartScope = smartScopingSpec;
@@ -96,7 +91,7 @@ public class SimpleAttributeDefinition extends BaseAttributeDefinition implement
                        log.debug("Smart Scoping disabled for attribute (" + getId() + ").");
                }
 
                        log.debug("Smart Scoping disabled for attribute (" + getId() + ").");
                }
 
-               //Load a value handler
+               // Load a value handler
                String valueHandlerSpec = e.getAttribute("valueHandler");
 
                if (valueHandlerSpec != null && !valueHandlerSpec.equals("")) {
                String valueHandlerSpec = e.getAttribute("valueHandler");
 
                if (valueHandlerSpec != null && !valueHandlerSpec.equals("")) {
@@ -116,9 +111,8 @@ public class SimpleAttributeDefinition extends BaseAttributeDefinition implement
                                                        + getId() + ") could not be loaded.");
                                }
                        } else {
                                                        + getId() + ") could not be loaded.");
                                }
                        } else {
-                               log
-                                               .error("Specification of \"valueHandler\' cannot be used in combination with \"smartScope\".  Ignoring Value Handler for attribute ("
-                                                               + getId() + ").");
+                               log.error("Specification of \"valueHandler\' cannot be used in combination with \"smartScope\". "
+                                               + " Ignoring Value Handler for attribute (" + getId() + ").");
                        }
                }
 
                        }
                }
 
@@ -126,7 +120,7 @@ public class SimpleAttributeDefinition extends BaseAttributeDefinition implement
                        log.debug("Custom Value Handler enabled for attribute (" + getId() + ").");
                }
 
                        log.debug("Custom Value Handler enabled for attribute (" + getId() + ").");
                }
 
-               //Decide whether or not to allow empty string values
+               // Decide whether or not to allow empty string values
                String rawAllowEmpty = e.getAttribute("allowEmpty");
                if (rawAllowEmpty != null) {
                        if (rawAllowEmpty.equalsIgnoreCase("TRUE")) {
                String rawAllowEmpty = e.getAttribute("allowEmpty");
                if (rawAllowEmpty != null) {
                        if (rawAllowEmpty.equalsIgnoreCase("TRUE")) {
@@ -136,7 +130,7 @@ public class SimpleAttributeDefinition extends BaseAttributeDefinition implement
 
                log.debug("Allowal of empty string values is set to (" + allowEmpty + ") for attribute (" + getId() + ").");
 
 
                log.debug("Allowal of empty string values is set to (" + allowEmpty + ") for attribute (" + getId() + ").");
 
-               //Decide whether or not to force values to lower case
+               // Decide whether or not to force values to lower case
                String rawDownCase = e.getAttribute("downCase");
                if (rawDownCase != null) {
                        if (rawDownCase.equalsIgnoreCase("TRUE")) {
                String rawDownCase = e.getAttribute("downCase");
                if (rawDownCase != null) {
                        if (rawDownCase.equalsIgnoreCase("TRUE")) {
@@ -191,58 +185,4 @@ public class SimpleAttributeDefinition extends BaseAttributeDefinition implement
                }
                attribute.setResolved();
        }
                }
                attribute.setResolved();
        }
-
-       protected Object[] getValuesFromAttributes(Dependencies depends) {
-
-               Set results = new LinkedHashSet();
-
-               Iterator attrDependIt = attributeDependencyIds.iterator();
-               while (attrDependIt.hasNext()) {
-                       ResolverAttribute attribute = depends.getAttributeResolution((String) attrDependIt.next());
-                       if (attribute != null) {
-                               log.debug("Found value(s) for attribute (" + getId() + ").");
-                               for (Iterator iterator = attribute.getValues(); iterator.hasNext();) {
-                                       results.add(iterator.next());
-                               }
-                       } else {
-                               log.error("An attribute dependency of attribute (" + getId()
-                                               + ") was not included in the dependency chain.");
-                       }
-               }
-
-               if (results.isEmpty()) {
-                       log.debug("An attribute dependency of attribute (" + getId() + ") supplied no values.");
-               }
-               return results.toArray();
-       }
-
-       protected Object[] getValuesFromConnectors(Dependencies depends) {
-
-               Set results = new LinkedHashSet();
-
-               Iterator connectorDependIt = connectorDependencyIds.iterator();
-               while (connectorDependIt.hasNext()) {
-                       Attributes attrs = depends.getConnectorResolution((String) connectorDependIt.next());
-                       if (attrs != null) {
-                               Attribute attr = attrs.get(connectorMapping);
-                               if (attr != null) {
-                                       log.debug("Found value(s) for attribute (" + getId() + ").");
-                                       try {
-                                               NamingEnumeration valuesEnum = attr.getAll();
-                                               while (valuesEnum.hasMore()) {
-                                                       results.add(valuesEnum.next());
-                                               }
-                                       } catch (NamingException e) {
-                                               log.error("An problem was encountered resolving the dependencies of attribute (" + getId()
-                                                               + "): " + e);
-                                       }
-                               }
-                       }
-               }
-
-               if (results.isEmpty()) {
-                       log.debug("A connector dependency of attribute (" + getId() + ") supplied no values.");
-               }
-               return results.toArray();
-       }
 }
\ No newline at end of file
 }
\ No newline at end of file
diff --git a/src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/SimpleBaseAttributeDefinition.java b/src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/SimpleBaseAttributeDefinition.java
new file mode 100644 (file)
index 0000000..1af1188
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
+ * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
+ * provided that the following conditions are met: Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution, if any, must include the following acknowledgment: "This product includes software
+ * developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu>Internet2 Project.
+ * Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
+ * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor
+ * the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
+ * products derived from this software without specific prior written permission. For written permission, please contact
+ * shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the
+ * University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name, without prior
+ * written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS PROVIDED BY THE
+ * COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE
+ * DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO
+ * EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC.
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Contributed by SungGard SCT.
+ */
+
+package edu.internet2.middleware.shibboleth.aa.attrresolv.provider;
+
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+
+import org.apache.log4j.Logger;
+import org.w3c.dom.Element;
+
+import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeDefinitionPlugIn;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.Dependencies;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolutionPlugInException;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolverAttribute;
+
+/**
+ * This is an abstract class that all other attribute definitions in this package extend. It provides processing of
+ * common attributes as well as dependency resolutions.
+ * 
+ * @author <a href="mailto:vgoenka@sungardsct.com">Vishal Goenka </a>
+ */
+abstract class SimpleBaseAttributeDefinition extends BaseAttributeDefinition implements AttributeDefinitionPlugIn {
+
+       private static Logger log = Logger.getLogger(SimpleBaseAttributeDefinition.class.getName());
+
+       protected ValueHandler valueHandler;
+       protected String connectorMapping;
+
+       protected SimpleBaseAttributeDefinition(Element e) throws ResolutionPlugInException {
+
+               super(e);
+
+               String sourceName = e.getAttribute("sourceName");
+               if (sourceName == null || sourceName.equals("")) {
+                       int index = getId().lastIndexOf("#");
+                       if (index < 0) {
+                               index = getId().lastIndexOf(":");
+                               int slashIndex = getId().lastIndexOf("/");
+                               if (slashIndex > index) {
+                                       index = slashIndex;
+                               }
+                       }
+                       connectorMapping = getId().substring(index + 1);
+               } else {
+                       connectorMapping = sourceName;
+               }
+
+               String valueHandlerSpec = e.getAttribute("valueHandler");
+
+               if (valueHandlerSpec != null && !valueHandlerSpec.equals("")) {
+                       try {
+                               Class handlerClass = Class.forName(valueHandlerSpec);
+                               valueHandler = (ValueHandler) handlerClass.newInstance();
+                       } catch (ClassNotFoundException cnfe) {
+                               log.error("Value Handler implementation specified for attribute (" + getId() + ") cannot be found: "
+                                               + cnfe);
+                               throw new ResolutionPlugInException("Value Handler implementation specified for attribute (" + getId()
+                                               + ") cannot be found.");
+                       } catch (Exception oe) {
+                               log.error("Value Handler implementation specified for attribute (" + getId()
+                                               + ") coudl not be loaded: " + oe);
+                               throw new ResolutionPlugInException("Value Handler implementation specified for attribute (" + getId()
+                                               + ") could not be loaded.");
+                       }
+               }
+
+               if (valueHandler != null) {
+                       log.debug("Custom Value Handler enabled for attribute (" + getId() + ").");
+               }
+
+       }
+
+       protected Collection resolveDependencies(ResolverAttribute attribute, Principal principal, String requester,
+                       Dependencies depends) throws ResolutionPlugInException {
+
+               log.debug("Resolving attribute: (" + getId() + ")");
+               Set results = new LinkedHashSet();
+               if (!connectorDependencyIds.isEmpty()) {
+                       results.addAll(Arrays.asList(getValuesFromConnectors(depends)));
+               }
+
+               if (!attributeDependencyIds.isEmpty()) {
+                       results.addAll(Arrays.asList(getValuesFromAttributes(depends)));
+               }
+
+               if (lifeTime != -1) {
+                       attribute.setLifetime(lifeTime);
+               }
+
+               if (valueHandler != null) {
+                       attribute.registerValueHandler(valueHandler);
+               }
+
+               return results;
+       }
+
+       protected Object[] getValuesFromAttributes(Dependencies depends) {
+
+               Set results = new LinkedHashSet();
+
+               Iterator attrDependIt = attributeDependencyIds.iterator();
+               while (attrDependIt.hasNext()) {
+                       ResolverAttribute attribute = depends.getAttributeResolution((String) attrDependIt.next());
+                       if (attribute != null) {
+                               log.debug("Found value(s) for attribute (" + getId() + ").");
+                               for (Iterator iterator = attribute.getValues(); iterator.hasNext();) {
+                                       results.add(iterator.next());
+                               }
+                       } else {
+                               log.error("An attribute dependency of attribute (" + getId()
+                                               + ") was not included in the dependency chain.");
+                       }
+               }
+
+               if (results.isEmpty()) {
+                       log.debug("An attribute dependency of attribute (" + getId() + ") supplied no values.");
+               }
+               return results.toArray();
+       }
+
+       protected Object[] getValuesFromConnectors(Dependencies depends) {
+
+               Set results = new LinkedHashSet();
+
+               Iterator connectorDependIt = connectorDependencyIds.iterator();
+               while (connectorDependIt.hasNext()) {
+                       Attributes attrs = depends.getConnectorResolution((String) connectorDependIt.next());
+                       if (attrs != null) {
+                               Attribute attr = attrs.get(connectorMapping);
+                               if (attr != null) {
+                                       log.debug("Found value(s) for attribute (" + getId() + ").");
+                                       try {
+                                               NamingEnumeration valuesEnum = attr.getAll();
+                                               while (valuesEnum.hasMore()) {
+                                                       results.add(valuesEnum.next());
+                                               }
+                                       } catch (NamingException e) {
+                                               log.error("An problem was encountered resolving the dependencies of attribute (" + getId()
+                                                               + "): " + e);
+                                       }
+                               }
+                       }
+               }
+
+               if (results.isEmpty()) {
+                       log.debug("A connector dependency of attribute (" + getId() + ") supplied no values.");
+               }
+               return results.toArray();
+       }
+
+       protected String getString(Object value) {
+
+               // if (value instanceof String) return (String)value;
+               // This was inspired by the fact that certain attributes (such as userPassword, when read using JNDI) are
+               // returned
+               // from data connectors as byte [] rather than String, and doing a .toString() returns something like
+               // B[@aabljadj,
+               // which is a reference to the array, rather than the string value.
+               if (value instanceof byte[]) return new String((byte[]) value);
+               return value.toString();
+       }
+
+}
index b9677c5..26f8d90 100644 (file)
        <xs:element name="AttributeResolver">
                <xs:complexType>
                        <xs:choice minOccurs="1" maxOccurs="unbounded">
        <xs:element name="AttributeResolver">
                <xs:complexType>
                        <xs:choice minOccurs="1" maxOccurs="unbounded">
-                               <xs:element name="SimpleAttributeDefinition" minOccurs="0" 
-                                       maxOccurs="unbounded">
+                               <xs:element name="SimpleAttributeDefinition" minOccurs="0" maxOccurs="unbounded">
                                        <xs:complexType>
                                                <xs:complexContent>
                                        <xs:complexType>
                                                <xs:complexContent>
-                                                       <xs:extension 
-                                                               base="resolver:BaseAttributeDefinition">
-                                                               <xs:attribute name="sourceName" 
-                                                                       type="xs:string" use="optional"/>
-                                                               <xs:attribute name="smartScope" 
-                                                                       type="xs:string" use="optional"/>
-                                                               <xs:attribute name="valueHandler" 
-                                                                       type="xs:string" use="optional"/>
-                                                               <xs:attribute name="allowEmpty" 
-                                                                       type="xs:boolean" use="optional"/>
-                                                               <xs:attribute name="downCase" type="xs:boolean" 
-                                                                       use="optional"/>
+                                                       <xs:extension base="resolver:BaseAttributeDefinition">
+                                                               <xs:attribute name="sourceName" type="xs:string" use="optional"/>
+                                                               <xs:attribute name="smartScope" type="xs:string" use="optional"/>
+                                                               <xs:attribute name="valueHandler" type="xs:string" use="optional"/>
+                                                               <xs:attribute name="allowEmpty" type="xs:boolean" use="optional"/>
+                                                               <xs:attribute name="downCase" type="xs:boolean" use="optional"/>
                                                        </xs:extension>
                                                </xs:complexContent>
                                        </xs:complexType>
                                </xs:element>
                                                        </xs:extension>
                                                </xs:complexContent>
                                        </xs:complexType>
                                </xs:element>
-                               <xs:element name="PersistentIDAttributeDefinition" 
-                                       minOccurs="0" maxOccurs="unbounded">
+                               <xs:element name="PersistentIDAttributeDefinition" minOccurs="0" maxOccurs="unbounded">
                                        <xs:complexType>
                                                <xs:complexContent>
                                        <xs:complexType>
                                                <xs:complexContent>
-                                                       <xs:extension 
-                                                               base="resolver:BaseAttributeDefinition">
+                                                       <xs:extension base="resolver:BaseAttributeDefinition">
                                                                <xs:sequence>
                                                                        <xs:element name="Salt">
                                                                                <xs:complexType>
                                                                                        <xs:simpleContent>
                                                                                                <xs:extension base="xs:string">
                                                                <xs:sequence>
                                                                        <xs:element name="Salt">
                                                                                <xs:complexType>
                                                                                        <xs:simpleContent>
                                                                                                <xs:extension base="xs:string">
-                                                                                                       <xs:attribute 
-                                                                                                               name="keyStorePath" 
-                                                                                                               type="xs:string" 
+                                                                                                       <xs:attribute name="keyStorePath" type="xs:string" use="optional"/>
+                                                                                                       <xs:attribute name="keyStoreKeyAlias" type="xs:string" 
                                                                                                                use="optional"/>
                                                                                                                use="optional"/>
-                                                                                                       <xs:attribute 
-                                                                                                               name="keyStoreKeyAlias" 
-                                                                                                               type="xs:string" 
+                                                                                                       <xs:attribute name="keyStorePassword" type="xs:string" 
                                                                                                                use="optional"/>
                                                                                                                use="optional"/>
-                                                                                                       <xs:attribute 
-                                                                                                               name="keyStorePassword" 
-                                                                                                               type="xs:string" 
-                                                                                                               use="optional"/>
-                                                                                                       <xs:attribute 
-                                                                                                               name="keyStoreKeyPassword" 
-                                                                                                               type="xs:string" 
+                                                                                                       <xs:attribute name="keyStoreKeyPassword" type="xs:string" 
                                                                                                                use="optional"/>
                                                                                                </xs:extension>
                                                                                        </xs:simpleContent>
                                                                                </xs:complexType>
                                                                        </xs:element>
                                                                </xs:sequence>
                                                                                                                use="optional"/>
                                                                                                </xs:extension>
                                                                                        </xs:simpleContent>
                                                                                </xs:complexType>
                                                                        </xs:element>
                                                                </xs:sequence>
-                                                               <xs:attribute name="sourceName" 
-                                                                       type="xs:string" use="optional"/>
-                                                               <xs:attribute name="scope" type="xs:string" 
-                                                                       use="required"/>
+                                                               <xs:attribute name="sourceName" type="xs:string" use="optional"/>
+                                                               <xs:attribute name="scope" type="xs:string" use="required"/>
+                                                       </xs:extension>
+                                               </xs:complexContent>
+                                       </xs:complexType>
+                               </xs:element>
+                               <xs:element name="RegExAttributeDefinition">
+                                       <xs:complexType>
+                                               <xs:complexContent>
+                                                       <xs:extension base="resolver:BaseAttributeDefinition">
+                                                               <xs:attribute name="sourceName" type="xs:string" use="optional"/>
+                                                               <xs:attribute name="regex" type="xs:string" use="required"/>
+                                                               <xs:attribute name="replacement" type="xs:string" use="required"/>
+                                                               <xs:attribute name="ignoreCase" type="xs:boolean" use="optional" default="false"/>
+                                                               <xs:attribute name="partialMatch" type="xs:boolean" use="optional" default="false"/>
+                                                       </xs:extension>
+                                               </xs:complexContent>
+                                       </xs:complexType>
+                               </xs:element>
+                               <xs:element name="FormattedAttributeDefinition">
+                                       <xs:complexType>
+                                               <xs:complexContent>
+                                                       <xs:extension base="resolver:BaseAttributeDefinition">
+                                                               <xs:sequence>
+                                                                       <xs:element name="Source" type="resolver:FormatType"/>
+                                                                       <xs:element name="Target" type="resolver:FormatType"/>
+                                                               </xs:sequence>
+                                                               <xs:attribute name="sourceName" type="xs:string" use="optional"/>
+                                                               <xs:attribute name="skipIfSameFormat" type="xs:boolean" use="optional" default="false"/>
+                                                       </xs:extension>
+                                               </xs:complexContent>
+                                       </xs:complexType>
+                               </xs:element>
+                               <xs:element name="CompositeAttributeDefinition">
+                                       <xs:complexType>
+                                               <xs:complexContent>
+                                                       <xs:extension base="resolver:BaseAttributeDefinition">
+                                                               <xs:attribute name="format" type="xs:string" use="optional"/>
+                                                               <xs:attribute name="orderedSourceNames" type="xs:string" use="required"/>
+                                                       </xs:extension>
+                                               </xs:complexContent>
+                                       </xs:complexType>
+                               </xs:element>
+                               <xs:element name="MappedAttributeDefinition">
+                                       <xs:complexType>
+                                               <xs:complexContent>
+                                                       <xs:extension base="resolver:BaseAttributeDefinition">
+                                                               <xs:sequence>
+                                                                       <xs:element name="ValueMap" maxOccurs="unbounded">
+                                                                               <xs:complexType>
+                                                                                       <xs:attribute name="value" type="xs:string" use="required"/>
+                                                                                       <xs:attribute name="keyset" type="xs:string" use="required"/>
+                                                                                       <xs:attribute name="separator" type="xs:string" use="optional" default=","/>
+                                                                               </xs:complexType>
+                                                                       </xs:element>
+                                                               </xs:sequence>
+                                                               <xs:attribute name="sourceName" type="xs:string" use="optional"/>
+                                                               <xs:attribute name="ignoreCase" type="xs:boolean" use="optional" default="false"/>
+                                                               <xs:attribute name="defaultValue" type="xs:string" use="optional"/>
+                                                               <xs:attribute name="regex" type="xs:boolean" use="optional" default="false"/>
                                                        </xs:extension>
                                                </xs:complexContent>
                                        </xs:complexType>
                                </xs:element>
                                                        </xs:extension>
                                                </xs:complexContent>
                                        </xs:complexType>
                                </xs:element>
-                               <xs:element name="CustomAttributeDefinition" minOccurs="0" 
-                                       maxOccurs="unbounded">
+                               <xs:element name="CustomAttributeDefinition" minOccurs="0" maxOccurs="unbounded">
                                        <xs:complexType>
                                                <xs:complexContent>
                                        <xs:complexType>
                                                <xs:complexContent>
-                                                       <xs:extension 
-                                                               base="resolver:BaseAttributeDefinition">
-                                                               <xs:sequence minOccurs="0" 
-                                                                       maxOccurs="unbounded">
-                                                                       <xs:any namespace="##any" 
-                                                                               processContents="lax" minOccurs="0" 
+                                                       <xs:extension base="resolver:BaseAttributeDefinition">
+                                                               <xs:sequence minOccurs="0" maxOccurs="unbounded">
+                                                                       <xs:any namespace="##any" processContents="lax" minOccurs="0" 
                                                                                maxOccurs="unbounded"/>
                                                                </xs:sequence>
                                                                                maxOccurs="unbounded"/>
                                                                </xs:sequence>
-                                                               <xs:attribute name="class" type="xs:string" 
-                                                                       use="required"/>
-                                                               <xs:anyAttribute namespace="##any" 
-                                                                       processContents="lax"/>
+                                                               <xs:attribute name="class" type="xs:string" use="required"/>
+                                                               <xs:anyAttribute namespace="##any" processContents="lax"/>
                                                        </xs:extension>
                                                </xs:complexContent>
                                        </xs:complexType>
                                </xs:element>
                                                        </xs:extension>
                                                </xs:complexContent>
                                        </xs:complexType>
                                </xs:element>
-                               <xs:element name="CustomDataConnector" minOccurs="0" 
-                                       maxOccurs="unbounded">
+                               <xs:element name="CustomDataConnector" minOccurs="0" maxOccurs="unbounded">
                                        <xs:complexType>
                                                <xs:complexContent>
                                                        <xs:extension base="resolver:BaseDataConnector">
                                                                <xs:sequence>
                                        <xs:complexType>
                                                <xs:complexContent>
                                                        <xs:extension base="resolver:BaseDataConnector">
                                                                <xs:sequence>
-                                                                       <xs:any namespace="##any" 
-                                                                               processContents="lax" minOccurs="0" 
+                                                                       <xs:any namespace="##any" processContents="lax" minOccurs="0" 
                                                                                maxOccurs="unbounded"/>
                                                                </xs:sequence>
                                                                                maxOccurs="unbounded"/>
                                                                </xs:sequence>
-                                                               <xs:attribute name="class" type="xs:string" 
-                                                                       use="required"/>
-                                                               <xs:anyAttribute namespace="##any" 
-                                                                       processContents="lax"/>
+                                                               <xs:attribute name="class" type="xs:string" use="required"/>
+                                                               <xs:anyAttribute namespace="##any" processContents="lax"/>
                                                        </xs:extension>
                                                </xs:complexContent>
                                        </xs:complexType>
                                </xs:element>
                                                        </xs:extension>
                                                </xs:complexContent>
                                        </xs:complexType>
                                </xs:element>
-                               <xs:element name="JNDIDirectoryDataConnector" minOccurs="0" 
-                                       maxOccurs="unbounded">
+                               <xs:element name="JNDIDirectoryDataConnector" minOccurs="0" maxOccurs="unbounded">
                                        <xs:complexType>
                                                <xs:complexContent>
                                                        <xs:extension base="resolver:BaseDataConnector">
                                                                <xs:sequence>
                                        <xs:complexType>
                                                <xs:complexContent>
                                                        <xs:extension base="resolver:BaseDataConnector">
                                                                <xs:sequence>
-                                                                       <xs:element ref="credentials:Credential" 
-                                                                               minOccurs="0" maxOccurs="1"/>
+                                                                       <xs:element ref="credentials:Credential" minOccurs="0" maxOccurs="1"/>
                                                                        <xs:element name="Search">
                                                                                <xs:complexType>
                                                                                        <xs:sequence minOccurs="0">
                                                                                                <xs:element name="Controls">
                                                                                                        <xs:complexType>
                                                                        <xs:element name="Search">
                                                                                <xs:complexType>
                                                                                        <xs:sequence minOccurs="0">
                                                                                                <xs:element name="Controls">
                                                                                                        <xs:complexType>
-                                                                                                               <xs:attribute 
-                                                                                                                       name="searchScope" 
-                                                                                                                       type="xs:string" 
-                                                                                                                       use="optional"/>
-                                                                                                               <xs:attribute 
-                                                                                                                       name="timeLimit" 
-                                                                                                                       type="xs:int" 
-                                                                                                                       use="optional"/>
-                                                                                                               <xs:attribute 
-                                                                                                                       name="returningObjects" 
-                                                                                                                       type="xs:boolean" 
+                                                                                                               <xs:attribute name="searchScope" type="xs:string" 
                                                                                                                        use="optional"/>
                                                                                                                        use="optional"/>
-                                                                                                               <xs:attribute 
-                                                                                                                       name="linkDereferencing" 
-                                                                                                                       type="xs:boolean" 
+                                                                                                               <xs:attribute name="timeLimit" type="xs:int" use="optional"/>
+                                                                                                               <xs:attribute name="returningObjects" type="xs:boolean" 
                                                                                                                        use="optional"/>
                                                                                                                        use="optional"/>
-                                                                                                               <xs:attribute 
-                                                                                                                       name="countLimit" 
-                                                                                                                       type="xs:long" 
+                                                                                                               <xs:attribute name="linkDereferencing" type="xs:boolean" 
                                                                                                                        use="optional"/>
                                                                                                                        use="optional"/>
+                                                                                                               <xs:attribute name="countLimit" type="xs:long" use="optional"/>
                                                                                                        </xs:complexType>
                                                                                                </xs:element>
                                                                                        </xs:sequence>
                                                                                                        </xs:complexType>
                                                                                                </xs:element>
                                                                                        </xs:sequence>
-                                                                                       <xs:attribute name="filter" 
-                                                                                               type="xs:string" 
-                                                                                               use="required"/>
+                                                                                       <xs:attribute name="filter" type="xs:string" use="required"/>
                                                                                </xs:complexType>
                                                                        </xs:element>
                                                                                </xs:complexType>
                                                                        </xs:element>
-                                                                       <xs:element name="Property" 
-                                                                               maxOccurs="unbounded">
+                                                                       <xs:element name="Property" maxOccurs="unbounded">
                                                                                <xs:complexType>
                                                                                <xs:complexType>
-                                                                                       <xs:attribute name="name" 
-                                                                                               type="xs:string" 
-                                                                                               use="required"/>
-                                                                                       <xs:attribute name="value" 
-                                                                                               type="xs:string" 
-                                                                                               use="required"/>
+                                                                                       <xs:attribute name="name" type="xs:string" use="required"/>
+                                                                                       <xs:attribute name="value" type="xs:string" use="required"/>
                                                                                </xs:complexType>
                                                                        </xs:element>
                                                                </xs:sequence>
                                                                                </xs:complexType>
                                                                        </xs:element>
                                                                </xs:sequence>
-                                                               <xs:attribute name="useStartTls" 
-                                                                       type="xs:boolean" use="optional" 
-                                                                       default="false"/>
+                                                               <xs:attribute name="useStartTls" type="xs:boolean" use="optional" default="false"/>
                                                        </xs:extension>
                                                </xs:complexContent>
                                        </xs:complexType>
                                </xs:element>
                                                        </xs:extension>
                                                </xs:complexContent>
                                        </xs:complexType>
                                </xs:element>
-                               <xs:element name="JDBCDataConnector" minOccurs="0" 
-                                       maxOccurs="unbounded">
+                               <xs:element name="JDBCDataConnector" minOccurs="0" maxOccurs="unbounded">
                                        <xs:complexType>
                                                <xs:complexContent>
                                                        <xs:extension base="resolver:BaseDataConnector">
                                                                <xs:sequence>
                                                                        <xs:element name="Query" type="xs:string"/>
                                        <xs:complexType>
                                                <xs:complexContent>
                                                        <xs:extension base="resolver:BaseDataConnector">
                                                                <xs:sequence>
                                                                        <xs:element name="Query" type="xs:string"/>
-                                                                       <xs:element name="AttributeExtractor" 
-                                                                               minOccurs="0">
+                                                                       <xs:element name="AttributeExtractor" minOccurs="0">
                                                                                <xs:complexType>
                                                                                <xs:complexType>
-                                                                                       <xs:attribute name="class" 
-                                                                                               type="xs:string" 
-                                                                                               use="required"/>
+                                                                                       <xs:attribute name="class" type="xs:string" use="required"/>
                                                                                </xs:complexType>
                                                                        </xs:element>
                                                                                </xs:complexType>
                                                                        </xs:element>
-                                                                       <xs:element name="StatementCreator" 
-                                                                               minOccurs="0">
+                                                                       <xs:element name="StatementCreator" minOccurs="0">
                                                                                <xs:complexType>
                                                                                <xs:complexType>
-                                                                                       <xs:sequence minOccurs="0" 
-                                                                                               maxOccurs="unbounded">
-                                                                                               <xs:any namespace="##any" 
-                                                                                                       processContents="lax" 
-                                                                                                       minOccurs="0" 
+                                                                                       <xs:sequence minOccurs="0" maxOccurs="unbounded">
+                                                                                               <xs:any namespace="##any" processContents="lax" minOccurs="0" 
                                                                                                        maxOccurs="unbounded"/>
                                                                                        </xs:sequence>
                                                                                                        maxOccurs="unbounded"/>
                                                                                        </xs:sequence>
-                                                                                       <xs:attribute name="class" 
-                                                                                               type="xs:string" 
-                                                                                               use="required"/>
-                                                                                       <xs:anyAttribute namespace="##any" 
-                                                                                               processContents="lax"/>
+                                                                                       <xs:attribute name="class" type="xs:string" use="required"/>
+                                                                                       <xs:anyAttribute namespace="##any" processContents="lax"/>
                                                                                </xs:complexType>
                                                                        </xs:element>
                                                                                </xs:complexType>
                                                                        </xs:element>
-                                                                       <xs:element name="Property" minOccurs="0" 
-                                                                               maxOccurs="unbounded">
+                                                                       <xs:element name="Property" minOccurs="0" maxOccurs="unbounded">
                                                                                <xs:complexType>
                                                                                <xs:complexType>
-                                                                                       <xs:attribute name="name" 
-                                                                                               type="xs:string" 
-                                                                                               use="required"/>
-                                                                                       <xs:attribute name="value" 
-                                                                                               type="xs:string" 
-                                                                                               use="required"/>
+                                                                                       <xs:attribute name="name" type="xs:string" use="required"/>
+                                                                                       <xs:attribute name="value" type="xs:string" use="required"/>
                                                                                </xs:complexType>
                                                                        </xs:element>
                                                                </xs:sequence>
                                                                                </xs:complexType>
                                                                        </xs:element>
                                                                </xs:sequence>
-                                                               <xs:attribute name="dbURL" type="xs:string" 
-                                                                       use="required"/>
-                                                               <xs:attribute name="dbDriver" type="xs:string" 
-                                                                       use="optional"/>
-                                                               <xs:attribute name="validationQuery" 
-                                                                       type="xs:string" use="optional" 
+                                                               <xs:attribute name="dbURL" type="xs:string" use="required"/>
+                                                               <xs:attribute name="dbDriver" type="xs:string" use="optional"/>
+                                                               <xs:attribute name="validationQuery" type="xs:string" use="optional" 
                                                                        default="select 1"/>
                                                                        default="select 1"/>
-                                                               <xs:attribute name="maxActive" 
-                                                                       type="xs:integer" use="optional"/>
-                                                               <xs:attribute name="maxIdle" type="xs:integer" 
-                                                                       use="optional"/>
-                                                               <xs:attribute name="maxWait" type="xs:integer" 
-                                                                       use="optional"/>
-                                                               <xs:attribute name="minResultSet" 
-                                                                       type="xs:integer" use="optional"/>
-                                                               <xs:attribute name="maxResultSet" 
-                                                                       type="xs:integer" use="optional"/>
-                                                               <xs:attribute name="retryInterval" 
-                                                                       type="xs:integer" use="optional"/>
+                                                               <xs:attribute name="maxActive" type="xs:integer" use="optional"/>
+                                                               <xs:attribute name="maxIdle" type="xs:integer" use="optional"/>
+                                                               <xs:attribute name="maxWait" type="xs:integer" use="optional"/>
+                                                               <xs:attribute name="minResultSet" type="xs:integer" use="optional"/>
+                                                               <xs:attribute name="maxResultSet" type="xs:integer" use="optional"/>
+                                                               <xs:attribute name="retryInterval" type="xs:integer" use="optional"/>
                                                        </xs:extension>
                                                </xs:complexContent>
                                        </xs:complexType>
                                                        </xs:extension>
                                                </xs:complexContent>
                                        </xs:complexType>
                <xs:attribute name="propagateErrors" type="xs:boolean" use="optional" 
                        default="true"/>
        </xs:complexType>
                <xs:attribute name="propagateErrors" type="xs:boolean" use="optional" 
                        default="true"/>
        </xs:complexType>
+       <xs:complexType name="FormatType">
+ <xs:attribute name="format" type="xs:string" use="required"/>
+ <xs:attribute name="pattern" type="xs:string" use="required"/>
+</xs:complexType>
 </xs:schema>
 </xs:schema>
diff --git a/tests/edu/internet2/middleware/shibboleth/aa/attrresolv/AttributesFile.java b/tests/edu/internet2/middleware/shibboleth/aa/attrresolv/AttributesFile.java
new file mode 100644 (file)
index 0000000..54f34c7
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
+ * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
+ * provided that the following conditions are met: Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution, if any, must include the following acknowledgment: "This product includes software
+ * developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu>Internet2 Project.
+ * Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
+ * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor
+ * the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
+ * products derived from this software without specific prior written permission. For written permission, please contact
+ * shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the
+ * University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name, without prior
+ * written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS PROVIDED BY THE
+ * COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE
+ * DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO
+ * EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC.
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Contributed by SungGard SCT.
+ */
+
+package edu.internet2.middleware.shibboleth.aa.attrresolv;
+
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.LineNumberReader;
+
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+
+import org.apache.log4j.Logger;
+import org.opensaml.SAMLException;
+
+import edu.internet2.middleware.shibboleth.aa.AAAttribute;
+import edu.internet2.middleware.shibboleth.aa.AAAttributeSet;
+
+/**
+ * The AttributesFile reads attributes specified in a file as name-value pairs separated by an 'equals' sign (=)
+ * Multiple values of an attribute may be specified using multiple pairs with the same attribute name. Multi-valued
+ * attributes can be read as ordered or unordered.
+ * 
+ * @author <a href="mailto:vgoenka@sungardsct.com">Vishal Goenka </a>
+ */
+
+public class AttributesFile {
+
+       private static Logger log = Logger.getLogger(AttributesFile.class.getName());
+       private String datafile;
+       private FileReader freader = null;
+       private LineNumberReader linereader = null;
+
+       public AttributesFile(String datafile) {
+
+               this.datafile = datafile;
+       }
+
+       private void open() throws IOException {
+
+               freader = new FileReader(datafile);
+               linereader = new LineNumberReader(freader);
+       }
+
+       private void close() {
+
+               try {
+                       if (freader != null) freader.close();
+               } catch (Exception e) {
+                       log.warn("Unexpected error when closing file: " + datafile + " -- " + e.getMessage());
+               }
+       }
+
+       private AVPair readAV() throws IOException {
+
+               AVPair av = null;
+               do {
+                       String line = linereader.readLine();
+                       if (line == null) break;
+
+                       line = line.trim();
+                       // Ignore comments and empty lines
+                       if ((line.length() == 0) || (line.charAt(0) == '#')) continue;
+
+                       int index = line.indexOf("=");
+                       if (index == -1)
+                               throw new IOException("'=' not specified in " + datafile + ":" + linereader.getLineNumber());
+                       String attrib = line.substring(0, index).trim();
+                       String value = line.substring(index + 1).trim();
+                       if ((attrib == null) || (attrib.length() == 0))
+                               throw new IOException("Empty attribute name in " + datafile + ":" + linereader.getLineNumber());
+
+                       if (value == null) value = "";
+
+                       av = new AVPair(attrib, value);
+               } while (av == null);
+               return av;
+       }
+
+       public synchronized Attributes readAttributes(boolean ordered) throws IOException {
+
+               open();
+               try {
+                       BasicAttributes attributes = new BasicAttributes();
+                       AVPair av = readAV();
+                       while (av != null) {
+                               BasicAttribute ba = (BasicAttribute) attributes.get(av.name);
+                               if (ba == null) {
+                                       ba = new BasicAttribute(av.name, ordered);
+                                       attributes.put(ba);
+                               }
+                               ba.add(av.value);
+                               av = readAV();
+                       }
+                       return attributes;
+               } finally {
+                       close();
+               }
+       }
+
+       public synchronized ResolverAttributeSet getResolverAttributes(boolean returnValues) throws IOException,
+                       SAMLException {
+
+               open();
+               try {
+                       AAAttributeSet attributes = new AAAttributeSet();
+                       AVPair av = readAV();
+                       while (av != null) {
+                               AAAttribute attr = (AAAttribute) attributes.getByName(av.name);
+                               if (attr == null) {
+                                       // The intern() is to work-around the bug in AAAttribute.equals() where the name of the
+                                       // attribute is compared
+                                       // using "==" rather than "equals" ...
+                                       attr = new AAAttribute(av.name.intern());
+                                       attributes.add(attr);
+                               }
+                               if (returnValues) {
+                                       attr.addValue(av.value);
+                               }
+                               av = readAV();
+                       }
+                       return attributes;
+               } finally {
+                       close();
+               }
+       }
+
+       private class AVPair {
+
+               String name;
+               String value;
+
+               public AVPair(String a, String v) {
+
+                       name = a;
+                       value = v;
+               }
+       }
+
+}
\ No newline at end of file
diff --git a/tests/edu/internet2/middleware/shibboleth/aa/attrresolv/FileConnector.java b/tests/edu/internet2/middleware/shibboleth/aa/attrresolv/FileConnector.java
new file mode 100644 (file)
index 0000000..3c1239d
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
+ * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
+ * provided that the following conditions are met: Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution, if any, must include the following acknowledgment: "This product includes software
+ * developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu>Internet2 Project.
+ * Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
+ * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor
+ * the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
+ * products derived from this software without specific prior written permission. For written permission, please contact
+ * shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the
+ * University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name, without prior
+ * written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS PROVIDED BY THE
+ * COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE
+ * DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO
+ * EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC.
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Contributed by SungGard SCT.
+ */
+
+package edu.internet2.middleware.shibboleth.aa.attrresolv;
+
+import java.security.Principal;
+
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+
+import org.apache.log4j.Logger;
+import org.w3c.dom.Element;
+
+import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.BaseResolutionPlugIn;
+
+/**
+ * The FileConnector essentially returns the same attribute values for all principals/requestors. The returned
+ * attributes are specified in a file as name-value pairs separated by an 'equals' sign (=). The datafile is specified
+ * as an attribute on the definition of the FileConnector. Only one attribute, namely the eduPersonPrincipalName may
+ * take different values for different principals. If this attribute is not specified in the properties file (datafile),
+ * the principal name passed to the resolver is returned as EPPN. Multiple values of an attribute may be specified using
+ * multiple pairs with the same attribute name. Multi-valued attributes are not considered ordered by default (to
+ * emulate LDAP data connector) unless the attribute 'ordered' is set to true.
+ * 
+ * @author <a href="mailto:vgoenka@sungardsct.com">Vishal Goenka </a>
+ */
+
+public class FileConnector extends BaseResolutionPlugIn implements DataConnectorPlugIn {
+
+       private static Logger log = Logger.getLogger(FileConnector.class.getName());
+       private Attributes attributes;
+
+       public FileConnector(Element e) throws ResolutionPlugInException {
+
+               super(e);
+               if (!e.hasAttribute("datafile"))
+                       throw new ResolutionPlugInException("datafile MUST be specified for FileConnector");
+               String datafile = e.getAttribute("datafile");
+               boolean ordered = false;
+               if (e.hasAttribute("ordered")) ordered = Boolean.valueOf(e.getAttribute("ordered")).booleanValue();
+
+               try {
+                       attributes = (new AttributesFile(datafile)).readAttributes(ordered);
+               } catch (Exception ex) {
+                       log.error("Failed to read datafile <" + datafile + "> - " + ex.getMessage(), ex);
+                       throw new ResolutionPlugInException(ex.getMessage());
+               }
+       }
+
+       /**
+        * @see edu.internet2.middleware.shibboleth.aa.attrresolv.DataConnectorPlugIn#resolve(Principal)
+        */
+       public Attributes resolve(Principal principal, String requester, Dependencies depends) {
+
+               log.debug("Resolving connector: (" + getId() + ")");
+               log.debug(getId() + " resolving for principal: (" + principal.getName() + ")");
+
+               BasicAttributes attrs = (BasicAttributes) attributes.clone();
+               BasicAttribute eppn = (BasicAttribute) attrs.get("eduPersonPrincipalName");
+               if (eppn == null) {
+                       attrs.put(new BasicAttribute("eduPersonPrincipalName", principal.getName()));
+               }
+               return attrs;
+       }
+
+       /**
+        * @see edu.internet2.middleware.shibboleth.aa.attrresolv.DataConnectorPlugIn#getFailoverDependencyId()
+        */
+       public String getFailoverDependencyId() {
+
+               return null;
+       }
+}
\ No newline at end of file
index 6dc5919..a919ef5 100644 (file)
@@ -1,56 +1,36 @@
-/* 
- * The Shibboleth License, Version 1. 
- * Copyright (c) 2002 
- * University Corporation for Advanced Internet Development, Inc. 
- * All rights reserved
- * 
- * 
- * Redistribution and use in source and binary forms, with or without 
- * modification, are permitted provided that the following conditions are met:
- * 
- * Redistributions of source code must retain the above copyright notice, this 
- * list of conditions and the following disclaimer.
- * 
- * Redistributions in binary form must reproduce the above copyright notice, 
- * this list of conditions and the following disclaimer in the documentation 
- * and/or other materials provided with the distribution, if any, must include 
- * the following acknowledgment: "This product includes software developed by 
- * the University Corporation for Advanced Internet Development 
- * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement 
- * may appear in the software itself, if and wherever such third-party 
- * acknowledgments normally appear.
- * 
- * Neither the name of Shibboleth nor the names of its contributors, nor 
- * Internet2, nor the University Corporation for Advanced Internet Development, 
- * Inc., nor UCAID may be used to endorse or promote products derived from this 
- * software without specific prior written permission. For written permission, 
- * please contact shibboleth@shibboleth.org
- * 
- * Products derived from this software may not be called Shibboleth, Internet2, 
- * UCAID, or the University Corporation for Advanced Internet Development, nor 
- * may Shibboleth appear in their name, without prior written permission of the 
- * University Corporation for Advanced Internet Development.
- * 
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
- * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
- * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK 
- * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. 
- * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY 
- * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, 
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+/*
+ * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
+ * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
+ * provided that the following conditions are met: Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution, if any, must include the following acknowledgment: "This product includes software
+ * developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu>Internet2 Project.
+ * Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
+ * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor
+ * the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
+ * products derived from this software without specific prior written permission. For written permission, please contact
+ * shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the
+ * University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name, without prior
+ * written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS PROVIDED BY THE
+ * COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE
+ * DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO
+ * EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC.
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 package edu.internet2.middleware.shibboleth.aa.attrresolv;
 
 import java.io.File;
  */
 
 package edu.internet2.middleware.shibboleth.aa.attrresolv;
 
 import java.io.File;
+import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
 
 import junit.framework.TestCase;
 
 
 import junit.framework.TestCase;
 
@@ -59,21 +39,28 @@ import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
 import org.opensaml.SAMLException;
 
 import org.apache.log4j.Logger;
 import org.opensaml.SAMLException;
 
-
 import edu.internet2.middleware.shibboleth.aa.AAAttribute;
 import edu.internet2.middleware.shibboleth.aa.AAAttributeSet;
 import edu.internet2.middleware.shibboleth.aa.AAAttribute;
 import edu.internet2.middleware.shibboleth.aa.AAAttributeSet;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolverAttributeSet.ResolverAttributeIterator;
 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.ScopedStringValueHandler;
 import edu.internet2.middleware.shibboleth.common.LocalPrincipal;
 
 /**
  * Validation suite for the <code>AttributeResolver</code>.
  * 
 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.ScopedStringValueHandler;
 import edu.internet2.middleware.shibboleth.common.LocalPrincipal;
 
 /**
  * Validation suite for the <code>AttributeResolver</code>.
  * 
- * @ author Walter Hoehn(wassa@columbia.edu)
+ * @author Walter Hoehn(wassa@columbia.edu)
+ * @author Vishal Goenka
  */
 
 public class ResolverTests extends TestCase {
 
  */
 
 public class ResolverTests extends TestCase {
 
+       private static Logger log = Logger.getLogger(ResolverTests.class.getName());
+       // Simple explanatory booleans, which are helpful when passed in functions as compared to true/false
+       private static final boolean DO_SORT = true;
+       private static final boolean DO_NOT_SORT = false;
+
        public ResolverTests(String name) {
        public ResolverTests(String name) {
+
                super(name);
                BasicConfigurator.resetConfiguration();
                BasicConfigurator.configure();
                super(name);
                BasicConfigurator.resetConfiguration();
                BasicConfigurator.configure();
@@ -81,6 +68,7 @@ public class ResolverTests extends TestCase {
        }
 
        public static void main(String[] args) {
        }
 
        public static void main(String[] args) {
+
                junit.textui.TestRunner.run(ResolverTests.class);
                BasicConfigurator.configure();
                Logger.getRootLogger().setLevel(Level.OFF);
                junit.textui.TestRunner.run(ResolverTests.class);
                BasicConfigurator.configure();
                Logger.getRootLogger().setLevel(Level.OFF);
@@ -90,6 +78,7 @@ public class ResolverTests extends TestCase {
         * @see junit.framework.TestCase#setUp()
         */
        protected void setUp() throws Exception {
         * @see junit.framework.TestCase#setUp()
         */
        protected void setUp() throws Exception {
+
                super.setUp();
        }
 
                super.setUp();
        }
 
@@ -99,19 +88,13 @@ public class ResolverTests extends TestCase {
                        File file = new File("data/resolver1.xml");
                        AttributeResolver ar = new AttributeResolver(file.toURL().toString());
 
                        File file = new File("data/resolver1.xml");
                        AttributeResolver ar = new AttributeResolver(file.toURL().toString());
 
-                       AAAttributeSet inputAttributes =
-                               new AAAttributeSet(
-                                       new AAAttribute[] {
-                                               new AAAttribute("urn:mace:dir:attribute-def:eduPersonNickName"),
-                                               new AAAttribute("urn:mace:dir:attribute-def:eduPersonEntitlement")});
+                       AAAttributeSet inputAttributes = new AAAttributeSet(new AAAttribute[]{
+                                       new AAAttribute("urn:mace:dir:attribute-def:eduPersonNickName"),
+                                       new AAAttribute("urn:mace:dir:attribute-def:eduPersonEntitlement")});
 
 
-                       AAAttributeSet outputAttributes =
-                               new AAAttributeSet(
-                                       new AAAttribute[] {
-                                                new AAAttribute(
-                                                       "urn:mace:dir:attribute-def:eduPersonEntitlement",
-                                                       new Object[] { "urn:mace:example.edu:exampleEntitlement" })
-                                               });
+                       AAAttributeSet outputAttributes = new AAAttributeSet(new AAAttribute[]{new AAAttribute(
+                                       "urn:mace:dir:attribute-def:eduPersonEntitlement",
+                                       new Object[]{"urn:mace:example.edu:exampleEntitlement"})});
 
                        ar.resolveAttributes(new LocalPrincipal("mytestuser"), "shar.example.edu", inputAttributes);
 
 
                        ar.resolveAttributes(new LocalPrincipal("mytestuser"), "shar.example.edu", inputAttributes);
 
@@ -125,7 +108,7 @@ public class ResolverTests extends TestCase {
                        fail("Error creating SAML Attribute: " + e.getMessage());
                }
        }
                        fail("Error creating SAML Attribute: " + e.getMessage());
                }
        }
-       
+
        public void testSmartScoping() {
 
                try {
        public void testSmartScoping() {
 
                try {
@@ -133,24 +116,17 @@ public class ResolverTests extends TestCase {
                        File file = new File("data/resolver2.xml");
                        AttributeResolver ar = new AttributeResolver(file.toURL().toString());
 
                        File file = new File("data/resolver2.xml");
                        AttributeResolver ar = new AttributeResolver(file.toURL().toString());
 
-                       AAAttributeSet inputAttributes =
-                               new AAAttributeSet(
-                                       new AAAttribute[] {
-                                               new AAAttribute("urn:mace:dir:attribute-def:eduPersonPrincipalName"),
-                                               new AAAttribute("foo")});
-
-                       AAAttributeSet outputAttributes = new AAAttributeSet(new AAAttribute[] {
-                               //Attribute should have scope appended to connector output
-                               new AAAttribute(
-                                       "urn:mace:dir:attribute-def:eduPersonPrincipalName",
-                                       new Object[] { "mytestuser@example.edu" },
-                                       new ScopedStringValueHandler("example.edu")),
-                               //Attribute should retain scope from connector output
-                               new AAAttribute(
-                                       "foo",
-                                       new Object[] { "bar@example.com" },
-                                       new ScopedStringValueHandler("example.edu"))
-                               });
+                       AAAttributeSet inputAttributes = new AAAttributeSet(new AAAttribute[]{
+                                       new AAAttribute("urn:mace:dir:attribute-def:eduPersonPrincipalName"), new AAAttribute("foo")});
+
+                       AAAttributeSet outputAttributes = new AAAttributeSet(
+                                       new AAAttribute[]{
+                                                       // Attribute should have scope appended to connector output
+                                                       new AAAttribute("urn:mace:dir:attribute-def:eduPersonPrincipalName",
+                                                                       new Object[]{"mytestuser@example.edu"}, new ScopedStringValueHandler("example.edu")),
+                                                       // Attribute should retain scope from connector output
+                                                       new AAAttribute("foo", new Object[]{"bar@example.com"}, new ScopedStringValueHandler(
+                                                                       "example.edu"))});
 
                        ar.resolveAttributes(new LocalPrincipal("mytestuser"), "shar.example.edu", inputAttributes);
                        assertEquals("Attribute Resolver returned unexpected attribute set.", inputAttributes, outputAttributes);
 
                        ar.resolveAttributes(new LocalPrincipal("mytestuser"), "shar.example.edu", inputAttributes);
                        assertEquals("Attribute Resolver returned unexpected attribute set.", inputAttributes, outputAttributes);
@@ -163,21 +139,21 @@ public class ResolverTests extends TestCase {
                        fail("Error creating SAML Attribute: " + e.getMessage());
                }
        }
                        fail("Error creating SAML Attribute: " + e.getMessage());
                }
        }
-       
+
        public void testExceptionForNoPlugIns() {
 
                try {
                        File file = new File("data/resolver3.xml");
                        new AttributeResolver(file.toURL().toString());
        public void testExceptionForNoPlugIns() {
 
                try {
                        File file = new File("data/resolver3.xml");
                        new AttributeResolver(file.toURL().toString());
-                       
+
                        fail("Attribute Resolver loaded even when no PlugIns were configured.");
                } catch (AttributeResolverException e) {
                        fail("Attribute Resolver loaded even when no PlugIns were configured.");
                } catch (AttributeResolverException e) {
-                       //This exception should be thrown, ignoring
+                       // This exception should be thrown, ignoring
                } catch (MalformedURLException e) {
                        fail("Error in test specification: " + e.getMessage());
                }
        }
                } catch (MalformedURLException e) {
                        fail("Error in test specification: " + e.getMessage());
                }
        }
-               
+
        public void testExceptionForNoValidPlugIns() {
 
                try {
        public void testExceptionForNoValidPlugIns() {
 
                try {
@@ -185,12 +161,12 @@ public class ResolverTests extends TestCase {
                        new AttributeResolver(file.toURL().toString());
                        fail("Attribute Resolver loaded even when no PlugIns were successfully registered.");
                } catch (AttributeResolverException e) {
                        new AttributeResolver(file.toURL().toString());
                        fail("Attribute Resolver loaded even when no PlugIns were successfully registered.");
                } catch (AttributeResolverException e) {
-                       //This exception should be thrown, ignoring
+                       // This exception should be thrown, ignoring
                } catch (MalformedURLException e) {
                        fail("Error in test specification: " + e.getMessage());
                }
        }
                } catch (MalformedURLException e) {
                        fail("Error in test specification: " + e.getMessage());
                }
        }
-               
+
        public void testFailToLoadCircularDependencies() {
 
                try {
        public void testFailToLoadCircularDependencies() {
 
                try {
@@ -198,12 +174,12 @@ public class ResolverTests extends TestCase {
                        new AttributeResolver(file.toURL().toString());
                        fail("Attribute Resolver loaded even when no only PlugIns with circular dependencies were configured.");
                } catch (AttributeResolverException e) {
                        new AttributeResolver(file.toURL().toString());
                        fail("Attribute Resolver loaded even when no only PlugIns with circular dependencies were configured.");
                } catch (AttributeResolverException e) {
-                       //This exception should be thrown, ignoring
+                       // This exception should be thrown, ignoring
                } catch (MalformedURLException e) {
                        fail("Error in test specification: " + e.getMessage());
                }
        }
                } catch (MalformedURLException e) {
                        fail("Error in test specification: " + e.getMessage());
                }
        }
-       
+
        public void testFailToLoadCircularDependenciesDeeper() {
 
                try {
        public void testFailToLoadCircularDependenciesDeeper() {
 
                try {
@@ -211,7 +187,7 @@ public class ResolverTests extends TestCase {
                        new AttributeResolver(file.toURL().toString());
                        fail("Attribute Resolver loaded even when no only PlugIns with circular dependencies were configured.");
                } catch (AttributeResolverException e) {
                        new AttributeResolver(file.toURL().toString());
                        fail("Attribute Resolver loaded even when no only PlugIns with circular dependencies were configured.");
                } catch (AttributeResolverException e) {
-                       //This exception should be thrown, ignoring
+                       // This exception should be thrown, ignoring
                } catch (MalformedURLException e) {
                        fail("Error in test specification: " + e.getMessage());
                }
                } catch (MalformedURLException e) {
                        fail("Error in test specification: " + e.getMessage());
                }
@@ -223,11 +199,10 @@ public class ResolverTests extends TestCase {
                        File file = new File("data/resolver7.xml");
                        AttributeResolver ar = new AttributeResolver(file.toURL().toString());
 
                        File file = new File("data/resolver7.xml");
                        AttributeResolver ar = new AttributeResolver(file.toURL().toString());
 
-                       AAAttributeSet inputAttributes = new AAAttributeSet(new AAAttribute[] { new AAAttribute("myAffiliation")});
+                       AAAttributeSet inputAttributes = new AAAttributeSet(new AAAttribute[]{new AAAttribute("myAffiliation")});
 
 
-                       AAAttributeSet outputAttributes =
-                               new AAAttributeSet(new AAAttribute[] { new AAAttribute("myAffiliation", new Object[] { "member" })
-                       });
+                       AAAttributeSet outputAttributes = new AAAttributeSet(new AAAttribute[]{new AAAttribute("myAffiliation",
+                                       new Object[]{"member"})});
 
                        ar.resolveAttributes(new LocalPrincipal("mytestuser"), "shar.example.edu", inputAttributes);
                        assertEquals("Attribute Resolver returned unexpected attribute set.", inputAttributes, outputAttributes);
 
                        ar.resolveAttributes(new LocalPrincipal("mytestuser"), "shar.example.edu", inputAttributes);
                        assertEquals("Attribute Resolver returned unexpected attribute set.", inputAttributes, outputAttributes);
@@ -247,25 +222,17 @@ public class ResolverTests extends TestCase {
                        File file = new File("data/resolver8.xml");
                        AttributeResolver ar = new AttributeResolver(file.toURL().toString());
 
                        File file = new File("data/resolver8.xml");
                        AttributeResolver ar = new AttributeResolver(file.toURL().toString());
 
-                       AAAttributeSet inputAttributes =
-                               new AAAttributeSet(
-                                       new AAAttribute[] {
-                                               new AAAttribute("urn:mace:dir:attribute-def:eduPersonPrincipalName"),
-                                               new AAAttribute("urn:mace:dir:attribute-def:eduPersonAffiliation"),
-                                               new AAAttribute("urn:mace:dir:attribute-def:eduPersonEntitlement")});
-
-                       AAAttributeSet outputAttributes =
-                               new AAAttributeSet(
-                                       new AAAttribute[] {
-                                               new AAAttribute(
-                                                       "urn:mace:dir:attribute-def:eduPersonPrincipalName",
-                                                       new Object[] { "mytestuser@example.edu" },
-                                                       new ScopedStringValueHandler("example.edu")),
-                                               new AAAttribute("urn:mace:dir:attribute-def:eduPersonAffiliation", new Object[] { "member" }),
-                                               new AAAttribute(
-                                                       "urn:mace:dir:attribute-def:eduPersonEntitlement",
-                                                       new Object[] { "urn:mace:example.edu:exampleEntitlement" })
-                                               });
+                       AAAttributeSet inputAttributes = new AAAttributeSet(new AAAttribute[]{
+                                       new AAAttribute("urn:mace:dir:attribute-def:eduPersonPrincipalName"),
+                                       new AAAttribute("urn:mace:dir:attribute-def:eduPersonAffiliation"),
+                                       new AAAttribute("urn:mace:dir:attribute-def:eduPersonEntitlement")});
+
+                       AAAttributeSet outputAttributes = new AAAttributeSet(new AAAttribute[]{
+                                       new AAAttribute("urn:mace:dir:attribute-def:eduPersonPrincipalName",
+                                                       new Object[]{"mytestuser@example.edu"}, new ScopedStringValueHandler("example.edu")),
+                                       new AAAttribute("urn:mace:dir:attribute-def:eduPersonAffiliation", new Object[]{"member"}),
+                                       new AAAttribute("urn:mace:dir:attribute-def:eduPersonEntitlement",
+                                                       new Object[]{"urn:mace:example.edu:exampleEntitlement"})});
 
                        ar.resolveAttributes(new LocalPrincipal("mytestuser"), "shar.example.edu", inputAttributes);
 
 
                        ar.resolveAttributes(new LocalPrincipal("mytestuser"), "shar.example.edu", inputAttributes);
 
@@ -279,25 +246,19 @@ public class ResolverTests extends TestCase {
                        fail("Error creating SAML Attribute: " + e.getMessage());
                }
        }
                        fail("Error creating SAML Attribute: " + e.getMessage());
                }
        }
-       
+
        public void testAttributeDependency() {
 
                try {
                        File file = new File("data/resolver9.xml");
                        AttributeResolver ar = new AttributeResolver(file.toURL().toString());
 
        public void testAttributeDependency() {
 
                try {
                        File file = new File("data/resolver9.xml");
                        AttributeResolver ar = new AttributeResolver(file.toURL().toString());
 
-                       AAAttributeSet inputAttributes =
-                               new AAAttributeSet(
-                                       new AAAttribute[] { new AAAttribute("urn:mace:dir:attribute-def:eduPersonScopedAffiliation")});
+                       AAAttributeSet inputAttributes = new AAAttributeSet(new AAAttribute[]{new AAAttribute(
+                                       "urn:mace:dir:attribute-def:eduPersonScopedAffiliation")});
 
 
-                       AAAttributeSet outputAttributes =
-                               new AAAttributeSet(
-                                       new AAAttribute[] {
-                                                new AAAttribute(
-                                                       "urn:mace:dir:attribute-def:eduPersonScopedAffiliation",
-                                                       new Object[] { "member@example.edu" },
-                                                       new ScopedStringValueHandler("example.edu"))
-                                               });
+                       AAAttributeSet outputAttributes = new AAAttributeSet(new AAAttribute[]{new AAAttribute(
+                                       "urn:mace:dir:attribute-def:eduPersonScopedAffiliation", new Object[]{"member@example.edu"},
+                                       new ScopedStringValueHandler("example.edu"))});
 
                        ar.resolveAttributes(new LocalPrincipal("mytestuser"), "shar.example.edu", inputAttributes);
 
 
                        ar.resolveAttributes(new LocalPrincipal("mytestuser"), "shar.example.edu", inputAttributes);
 
@@ -311,16 +272,15 @@ public class ResolverTests extends TestCase {
                        fail("Error creating SAML Attribute: " + e.getMessage());
                }
        }
                        fail("Error creating SAML Attribute: " + e.getMessage());
                }
        }
-       
+
        public void testMisLabeledDataConnector() {
 
                try {
                        File file = new File("data/resolver11.xml");
                        AttributeResolver ar = new AttributeResolver(file.toURL().toString());
 
        public void testMisLabeledDataConnector() {
 
                try {
                        File file = new File("data/resolver11.xml");
                        AttributeResolver ar = new AttributeResolver(file.toURL().toString());
 
-                       AAAttributeSet inputAttributes =
-                               new AAAttributeSet(
-                                       new AAAttribute[] { new AAAttribute("urn:mace:dir:attribute-def:eduPersonScopedAffiliation")});
+                       AAAttributeSet inputAttributes = new AAAttributeSet(new AAAttribute[]{new AAAttribute(
+                                       "urn:mace:dir:attribute-def:eduPersonScopedAffiliation")});
 
                        AAAttributeSet outputAttributes = new AAAttributeSet();
 
 
                        AAAttributeSet outputAttributes = new AAAttributeSet();
 
@@ -343,9 +303,8 @@ public class ResolverTests extends TestCase {
                        File file = new File("data/resolver10.xml");
                        AttributeResolver ar = new AttributeResolver(file.toURL().toString());
 
                        File file = new File("data/resolver10.xml");
                        AttributeResolver ar = new AttributeResolver(file.toURL().toString());
 
-                       AAAttributeSet inputAttributes =
-                               new AAAttributeSet(
-                                       new AAAttribute[] { new AAAttribute("urn:mace:dir:attribute-def:eduPersonScopedAffiliation")});
+                       AAAttributeSet inputAttributes = new AAAttributeSet(new AAAttribute[]{new AAAttribute(
+                                       "urn:mace:dir:attribute-def:eduPersonScopedAffiliation")});
 
                        AAAttributeSet outputAttributes = new AAAttributeSet();
 
 
                        AAAttributeSet outputAttributes = new AAAttributeSet();
 
@@ -362,29 +321,23 @@ public class ResolverTests extends TestCase {
                        fail("Error creating SAML Attribute: " + e.getMessage());
                }
        }
                        fail("Error creating SAML Attribute: " + e.getMessage());
                }
        }
+
        public void testMultiLevelAttributeDependency() {
 
                try {
                        File file = new File("data/resolver12.xml");
                        AttributeResolver ar = new AttributeResolver(file.toURL().toString());
 
        public void testMultiLevelAttributeDependency() {
 
                try {
                        File file = new File("data/resolver12.xml");
                        AttributeResolver ar = new AttributeResolver(file.toURL().toString());
 
-                       AAAttributeSet inputAttributes =
-                               new AAAttributeSet(
-                                       new AAAttribute[] {
-                                               new AAAttribute("urn:mace:dir:attribute-def:eduPersonScopedAffiliation"),
-                                               new AAAttribute("urn:mace:dir:attribute-def:eduPersonAffiliation"),
-                                               new AAAttribute("urn:mace:shibboleth:test:eduPersonAffiliation")});
-
-                       AAAttributeSet outputAttributes =
-                               new AAAttributeSet(
-                                       new AAAttribute[] {
-                                               new AAAttribute(
-                                                       "urn:mace:dir:attribute-def:eduPersonScopedAffiliation",
-                                                       new Object[] { "member@example.edu" },
-                                                       new ScopedStringValueHandler("example.edu")),
-                                               new AAAttribute("urn:mace:dir:attribute-def:eduPersonAffiliation", new Object[] { "member" }),
-                                               new AAAttribute("urn:mace:shibboleth:test:eduPersonAffiliation", new Object[] { "member" })
-                                       });
+                       AAAttributeSet inputAttributes = new AAAttributeSet(new AAAttribute[]{
+                                       new AAAttribute("urn:mace:dir:attribute-def:eduPersonScopedAffiliation"),
+                                       new AAAttribute("urn:mace:dir:attribute-def:eduPersonAffiliation"),
+                                       new AAAttribute("urn:mace:shibboleth:test:eduPersonAffiliation")});
+
+                       AAAttributeSet outputAttributes = new AAAttributeSet(new AAAttribute[]{
+                                       new AAAttribute("urn:mace:dir:attribute-def:eduPersonScopedAffiliation",
+                                                       new Object[]{"member@example.edu"}, new ScopedStringValueHandler("example.edu")),
+                                       new AAAttribute("urn:mace:dir:attribute-def:eduPersonAffiliation", new Object[]{"member"}),
+                                       new AAAttribute("urn:mace:shibboleth:test:eduPersonAffiliation", new Object[]{"member"})});
 
                        ar.resolveAttributes(new LocalPrincipal("mytestuser"), "shar.example.edu", inputAttributes);
 
 
                        ar.resolveAttributes(new LocalPrincipal("mytestuser"), "shar.example.edu", inputAttributes);
 
@@ -399,5 +352,118 @@ public class ResolverTests extends TestCase {
                }
        }
 
                }
        }
 
+       /**
+        * This method is reused by several tests that use different XML files as test data.
+        * 
+        * @param resolverFile
+        *            filename of a file containing data in the same format as resolver.xml.
+        * @param attributeFile
+        *            filename of a file containing output of the attribute resolution. The input file for attribute
+        *            resolution is specified in the resolverFile itself
+        * @param principal
+        *            name of the principal on whose behalf the resolution is done
+        * @param requester
+        *            the Shibboleth Target SHAR that is requesting the attribute resolution
+        */
+       private void simpleAttributeResolution(String resolverFile, String attributeFile, String principal,
+                       String requester, boolean sort) {
+
+               try {
+                       // Create the attribute resolver
+                       File file = new File(resolverFile);
+                       AttributeResolver ar = new AttributeResolver(file.toURL().toString());
+
+                       // Create the output attributes file
+                       AttributesFile attrFile = new AttributesFile(attributeFile);
+
+                       // Read only the attribute names from the output file. The values are set by the resolver
+                       ResolverAttributeSet attrsToBeResolved = attrFile.getResolverAttributes(false);
+                       ar.resolveAttributes(new LocalPrincipal(principal), requester, attrsToBeResolved);
+
+                       // Read the attribute names and values from the output file
+                       ResolverAttributeSet expectedAttributes = attrFile.getResolverAttributes(true);
+
+                       if (sort) {
+                               sort(attrsToBeResolved);
+                               sort(expectedAttributes);
+                       }
+                       // Ensure that the values set by the resolver are the same as the ones outlined in the output file
+                       assertEquals("Attribute Resolver returned unexpected attribute set.", expectedAttributes, attrsToBeResolved);
+               } catch (AttributeResolverException e) {
+                       fail("Couldn't load attribute resolver: " + e.getMessage());
+               } catch (MalformedURLException e) {
+                       fail("Error in test specification: " + e.getMessage());
+               } catch (IOException e) {
+                       fail("Error in test data: " + e.getMessage());
+               } catch (SAMLException e) {
+                       fail("Error creating SAML attribute: " + e.getMessage());
+               }
+       }
+
+       public void testAttrDef_RegEx_DN_CN_UID() {
+
+               simpleAttributeResolution("data/attr-regex.resolver.1.xml", "data/attr-regex.output.1", "RegExTestUser",
+                               "urn::test:luminis::sungardsct::com", DO_NOT_SORT);
+       }
+
+       public void testAttrDef_Mapped_PdsRole_EduPersonAffiliation() {
+
+               simpleAttributeResolution("data/attr-mapped.resolver.1.xml", "data/attr-mapped.output.1", "MappedTestUser",
+                               "urn::test:luminis::sungardsct::com", DO_SORT);
+       }
+
+       public void testAttrDef_Mapped_Role_EduPersonEntitlement() {
+
+               simpleAttributeResolution("data/attr-mapped.resolver.2.xml", "data/attr-mapped.output.2", "MappedTestUser",
+                               "urn::test:luminis::sungardsct::com", DO_SORT);
+       }
+
+       public void testAttrDef_Formatted_DateOfBirth() {
+
+               simpleAttributeResolution("data/attr-format.resolver.1.xml", "data/attr-format.output.1",
+                               "FormattedDateTestUser", "urn::test:luminis::sungardsct::com", DO_NOT_SORT);
+       }
+
+       public void testAttrDef_Formatted_Choice_GPA_Distinction() {
+
+               simpleAttributeResolution("data/attr-format.resolver.2.xml", "data/attr-format.output.2", "FormattedTestUser",
+                               "urn::test:luminis::sungardsct::com", DO_NOT_SORT);
+       }
+
+       public void testAttrDef_Composite_LabeledURI() {
+
+               simpleAttributeResolution("data/attr-composite.resolver.1.xml", "data/attr-composite.output.1",
+                               "CompositeTestUser", "urn::test:luminis::sungardsct::com", DO_NOT_SORT);
+       }
+
+       // Failing Test cases
+       // How to test that improperly configured definitions will fail to load. Can we check for the specific error?
+       // 
+       /**
+        * 1. RegEx - Custom Value Handler (reverses the characters) can be used 2. Mapped - Custom Value Handler (reverses
+        * the characters) can be used 3. Formatted - Custom Value Handler (reverses the characters) can be used 4.
+        * Composite - Custom Value Handler (reverses the characters) can be used - unordered values should fail - Behavior
+        * with unequal number of values
+        */
+
+       /**
+        * Sort the attribute values in the AAAttribute so that equals comparison works as intended
+        */
+       private void sort(ResolverAttributeSet attrSet) {
+
+               for (ResolverAttributeIterator iter = attrSet.resolverAttributeIterator(); iter.hasNext();) {
+                       ResolverAttribute attr = iter.nextResolverAttribute();
+                       if (attr instanceof AAAttribute) {
+                               ArrayList values = new ArrayList();
+                               for (Iterator valuesIterator = attr.getValues(); valuesIterator.hasNext();) {
+                                       values.add(valuesIterator.next());
+                               }
+                               Object[] sortedValues = values.toArray();
+                               Arrays.sort(sortedValues);
+
+                               ((AAAttribute) attr).setValues(sortedValues);
+                       }
+               }
+       }
 
 }
 
 }