[java-identity-provider] branch master updated: IDP-1413 - Clean up StoredID support classes/interfaces/beans

Scott Cantor cantor.2 at osu.edu
Tue Feb 19 10:57:18 EST 2019


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch master
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=cb727a9cc25e0898ae8e2f88f6f429c5dbf5322d

The following commit(s) were added to refs/heads/master by this push:
       new  cb727a9   IDP-1413 - Clean up StoredID support classes/interfaces/beans
cb727a9 is described below

commit cb727a9cc25e0898ae8e2f88f6f429c5dbf5322d
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Feb 19 10:57:11 2019 -0500

    IDP-1413 - Clean up StoredID support classes/interfaces/beans
    
    https://issues.shibboleth.net/jira/browse/IDP-1413
    
    Redesigned class hierarchy and updated parsers and wiring.
---
 .../idp/attribute/DurablePairwiseIdStore.java      |  72 ++++
 .../net/shibboleth/idp/attribute/PairwiseId.java   | 104 +++---
 .../shibboleth/idp/attribute/PairwiseIdStore.java  |  50 +++
 idp-attribute-impl/.checkstyle                     |  15 +
 idp-attribute-impl/.classpath                      |  38 ++
 idp-attribute-impl/.gitignore                      |  21 ++
 idp-attribute-impl/.project                        |  40 ++
 .../.settings/org.eclipse.core.resources.prefs     |   6 +
 .../.settings/org.eclipse.jdt.core.prefs           | 351 ++++++++++++++++++
 .../.settings/org.eclipse.jdt.ui.prefs             |  63 ++++
 .../.settings/org.eclipse.m2e.core.prefs           |   4 +
 idp-attribute-impl/checkstyle.xml                  | 116 ++++++
 idp-attribute-impl/pom.xml                         |  48 +++
 idp-attribute-impl/src/main/java/.gitkeep          |   0
 .../attribute/impl/ComputedPairwiseIdStore.java    |  75 ++--
 .../idp/attribute/impl/JDBCPairwiseIdStore.java    | 404 +++++++++++----------
 .../idp/attribute/impl/package-info.java           |  22 ++
 idp-attribute-impl/src/main/resources/.gitkeep     |   0
 idp-attribute-impl/src/test/java/.gitkeep          |   0
 .../impl/ComputedPairwiseIdStoreTest.java          | 168 +++++++++
 .../attribute/impl/JDBCPairwiseIdStoreTest.java    |  94 +++--
 .../src/test/resources/logback-test.xml            |  17 +
 .../shibboleth/idp/attribute/impl/DeleteStore.sql  |   1 +
 .../idp/attribute/impl/StoredIdStore.sql           |  11 +
 .../resolver/dc/impl/PairwiseIdDataConnector.java  | 310 ++++++++++++++++
 .../dc/impl/ComputedIDDataConnectorTest.java       | 115 +++---
 .../dc/impl/StoredIDDataConnectorTest.java         | 126 ++-----
 idp-attribute-resolver-spring/pom.xml              |  10 +
 .../dc/impl/ComputedIDDataConnectorParser.java     |  62 +++-
 ...ser.java => PairwiseIdDataConnectorParser.java} |  68 ++--
 .../dc/impl/StoredIDDataConnectorParser.java       |  81 ++---
 .../dc/ComputedIDDataConnectorParserTest.java      |  33 +-
 .../resolver/spring/dc/ManagedConnectionTest.java  |  47 +--
 .../spring/dc/StoredIDDataConnectorParserTest.java |  40 +-
 .../resources/system/conf/saml-nameid-system.xml   |  14 +-
 idp-parent/pom.xml                                 |   1 +
 idp-saml-impl/pom.xml                              |   5 +
 .../resolver/impl/ComputedIDDataConnector.java     | 237 ------------
 .../resolver/impl/StoredIDDataConnector.java       | 243 -------------
 .../impl/PersistentIdGenerationStrategy.java       |  48 ---
 .../idp/saml/nameid/impl/PersistentIdStoreEx.java  |  98 -----
 .../impl/PersistentSAML2NameIDGenerator.java       | 103 ++++--
 .../nameid/impl/StoredPersistentIdDecoder.java     |  42 ++-
 .../impl/StoredPersistentIdGenerationStrategy.java | 140 -------
 .../impl/PersistentSAML2NameIDGeneratorTest.java   | 181 ++-------
 .../nameid/impl/StoredPersistentIdDecoderTest.java |   9 +-
 46 files changed, 2106 insertions(+), 1627 deletions(-)

diff --git a/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/DurablePairwiseIdStore.java b/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/DurablePairwiseIdStore.java
new file mode 100644
index 0000000..d1cabe8
--- /dev/null
+++ b/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/DurablePairwiseIdStore.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute;
+
+import java.io.IOException;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Extended {@link PairwiseIdStore} interface that supports reversal, mutation, and deactivation features
+ * requiring durable storage.
+ * 
+ * @since 4.0.0
+ */
+public interface DurablePairwiseIdStore extends PairwiseIdStore {
+    
+    /**
+     * Populate the underlying principal/source fields for the input object based on the
+     * supplied values.
+     * 
+     * <p>The input object must contain values for issuer and recipient entityIDs and the
+     * pairwiseId itself, and the rest of the fields will be populated as applicable on
+     * output.</p>
+     * 
+     * <p>The object returned, if non-null, will at least contain the principal name and
+     * source system ID. It may be, but does not have to be, the same physical object
+     * used as input. The original input object should not be referenced further.</p>
+     * 
+     * @param pid object to populate
+     * 
+     * @return object for the given inputs or null if none exists
+     * @throws IOException if an error occurs accessing the store
+     */
+    @Nullable PairwiseId getByIssuedValue(@Nonnull final PairwiseId pid) throws IOException;
+    
+    /**
+     * Deactivate/revoke a pairwise ID.
+     * 
+     * <p>If the object's deactivation time field is null, then the current time is used.</p>
+     * 
+     * @param pid the object to deactivate/revoke
+     * 
+     * @throws IOException if there is an error updating the store
+     */
+    void deactivate(@Nonnull PairwiseId pid) throws IOException;
+
+    /**
+     * Attach a peer-supplied alias to a pairwise ID.
+     * 
+     * @param pid the object to update in storage
+     * 
+     * @throws IOException if there is an error updating the store
+     */
+    void attach(@Nonnull PairwiseId pid) throws IOException;
+    
+}
\ No newline at end of file
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/nameid/PersistentIdEntry.java b/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/PairwiseId.java
similarity index 62%
rename from idp-saml-api/src/main/java/net/shibboleth/idp/saml/nameid/PersistentIdEntry.java
rename to idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/PairwiseId.java
index bcff909..36400c1 100644
--- a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/nameid/PersistentIdEntry.java
+++ b/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/PairwiseId.java
@@ -15,50 +15,48 @@
  * limitations under the License.
  */
 
-package net.shibboleth.idp.saml.nameid;
-
-import java.io.Serializable;
-import java.sql.Timestamp;
+package net.shibboleth.idp.attribute;
 
 import javax.annotation.Nullable;
 
-/** Object representing a persistent identifier entry in storage. */
-public class PersistentIdEntry implements Serializable {
-
-    /** Serial version UID. */
-    private static final long serialVersionUID = -8711779466442306767L;
+/**
+ * Object representing a pairwise/targeted identifier entry as a storage input/output.
+ * 
+ * @since 4.0.0
+ */
+public class PairwiseId {
 
     /** ID of the entity that issued that identifier. */
-    @Nullable private String issuerEntityId;
+    @Nullable private String issuerEntityID;
 
     /** ID of the entity to which the identifier was issued. */
-    @Nullable private String recipientEntityId;
+    @Nullable private String recipientEntityID;
 
     /** Name of the principal represented by the identifier. */
     @Nullable private String principalName;
 
-    /** Underlying source ID of the entry. */
-    @Nullable private String sourceId;
+    /** Underlying unique key/ID of the principal. */
+    @Nullable private String sourceSystemId;
 
-    /** The persistent identifier. */
-    @Nullable private String persistentId;
+    /** The identifier. */
+    @Nullable private String pairwiseId;
 
-    /** ID, associated with the persistent identifier, provided by the peer. */
+    /** A secondary identifier attached to the record by the recipient. */
     @Nullable private String peerProvidedId;
 
     /** Time the identifier was created. */
-    @Nullable private Timestamp creationTime;
+    @Nullable private Long creationTime;
 
     /** Time the identifier was deactivated. */
-    @Nullable private Timestamp deactivationTime;
+    @Nullable private Long deactivationTime;
 
     /**
      * Get the ID of the entity that issued the identifier.
      * 
      * @return ID of the entity that issued the identifier
      */
-    @Nullable public String getIssuerEntityId() {
-        return issuerEntityId;
+    @Nullable public String getIssuerEntityID() {
+        return issuerEntityID;
     }
 
     /**
@@ -66,8 +64,8 @@ public class PersistentIdEntry implements Serializable {
      * 
      * @param id ID of the entity that issued the identifier
      */
-    public void setIssuerEntityId(@Nullable final String id) {
-        issuerEntityId = id;
+    public void setIssuerEntityID(@Nullable final String id) {
+        issuerEntityID = id;
     }
 
     /**
@@ -75,8 +73,8 @@ public class PersistentIdEntry implements Serializable {
      * 
      * @return ID of the entity to which the identifier was issued
      */
-    @Nullable public String getRecipientEntityId() {
-        return recipientEntityId;
+    @Nullable public String getRecipientEntityID() {
+        return recipientEntityID;
     }
 
     /**
@@ -84,8 +82,8 @@ public class PersistentIdEntry implements Serializable {
      * 
      * @param id ID of the entity to which the identifier was issued
      */
-    public void setRecipientEntityId(@Nullable final String id) {
-        recipientEntityId = id;
+    public void setRecipientEntityID(@Nullable final String id) {
+        recipientEntityID = id;
     }
 
     /**
@@ -107,39 +105,41 @@ public class PersistentIdEntry implements Serializable {
     }
 
     /**
-     * Get the source ID underlying the persistent identifier.
+     * Get the underlying unique key/ID from the source IDM system, which may be more
+     * stable than the "name".
      * 
-     * @return source ID underlying the persistent identifier
+     * @return the principal's unique key or ID
      */
-    @Nullable public String getSourceId() {
-        return sourceId;
+    @Nullable public String getSourceSystemId() {
+        return sourceSystemId;
     }
 
     /**
-     * Set the source ID underlying the persistent identifier.
+     * Set the underlying unique key/ID from the source IDM system, which may be more
+     * stable than the "name".
      * 
-     * @param id source ID underlying the persistent identifier
+     * @param id principal's unique key or ID
      */
-    public void setSourceId(@Nullable final String id) {
-        sourceId = id;
+    public void setSourceSystemId(@Nullable final String id) {
+        sourceSystemId = id;
     }
 
     /**
-     * Get the persistent identifier.
+     * Get the pairwise identifier.
      * 
-     * @return the persistent identifier
+     * @return the pairwise identifier
      */
-    @Nullable public String getPersistentId() {
-        return persistentId;
+    @Nullable public String getPairwiseId() {
+        return pairwiseId;
     }
 
     /**
-     * Set the persistent identifier.
+     * Set the pairwise identifier.
      * 
-     * @param id the persistent identifier
+     * @param id the pairwise identifier
      */
-    public void setPersistentId(@Nullable final String id) {
-        persistentId = id;
+    public void setPairwiseId(@Nullable final String id) {
+        pairwiseId = id;
     }
 
     /**
@@ -165,7 +165,7 @@ public class PersistentIdEntry implements Serializable {
      * 
      * @return time the identifier was created
      */
-    @Nullable public Timestamp getCreationTime() {
+    @Nullable public Long getCreationTime() {
         return creationTime;
     }
 
@@ -174,7 +174,7 @@ public class PersistentIdEntry implements Serializable {
      * 
      * @param time time the identifier was created
      */
-    public void setCreationTime(@Nullable final Timestamp time) {
+    public void setCreationTime(@Nullable final Long time) {
         creationTime = time;
     }
 
@@ -183,7 +183,7 @@ public class PersistentIdEntry implements Serializable {
      * 
      * @return time the identifier was deactivated
      */
-    @Nullable public Timestamp getDeactivationTime() {
+    @Nullable public Long getDeactivationTime() {
         return deactivationTime;
     }
 
@@ -192,22 +192,22 @@ public class PersistentIdEntry implements Serializable {
      * 
      * @param time the time the identifier was deactivated
      */
-    public void setDeactivationTime(@Nullable final Timestamp time) {
+    public void setDeactivationTime(@Nullable final Long time) {
         deactivationTime = time;
     }
 
     /** {@inheritDoc} */
     @Override
     public String toString() {
-        final StringBuilder stringForm = new StringBuilder("PersistentIdEntry{");
-        stringForm.append("persistentId:").append(persistentId).append(", ");
-        stringForm.append("localEntityId:").append(issuerEntityId).append(", ");
-        stringForm.append("recipientEntityId:").append(recipientEntityId).append(", ");
-        stringForm.append("sourceId:").append(sourceId).append(", ");
+        final StringBuilder stringForm = new StringBuilder("PairwiseId {");
+        stringForm.append("pairwiseId:").append(pairwiseId).append(", ");
+        stringForm.append("issuerEntityID:").append(issuerEntityID).append(", ");
+        stringForm.append("recipientEntityID:").append(recipientEntityID).append(", ");
+        stringForm.append("sourceSystemId:").append(sourceSystemId).append(", ");
         stringForm.append("principalName:").append(principalName).append(", ");
         stringForm.append("peerProvidedId:").append(peerProvidedId).append(", ");
         stringForm.append("creationTime:").append(creationTime).append(", ");
-        stringForm.append("deactivationTime:").append(deactivationTime).append(", ");
+        stringForm.append("deactivationTime:").append(deactivationTime);
         stringForm.append("}");
         return stringForm.toString();
     }
diff --git a/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/PairwiseIdStore.java b/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/PairwiseIdStore.java
new file mode 100644
index 0000000..2175bb8
--- /dev/null
+++ b/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/PairwiseIdStore.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute;
+
+import java.io.IOException;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Storage and retrieval interface for pairwise/targeted identifiers allowing for custom implementations.
+ * 
+ * @since 4.0.0
+ */
+public interface PairwiseIdStore {
+    
+    /**
+     * Populate the pairwise ID field for the input object based on the
+     * supplied values.
+     * 
+     * <p>The input object must contain values for issuer and recipient entityIDs and the
+     * principal name, and the pairwise ID will be populated as applicable on output.</p>
+     * 
+     * <p>The object returned, if non-null, may be, but does not have to be, the same physical
+     * object used as input. The original input object should not be referenced further.</p>
+     * 
+     * @param pid object to populate
+     * @param allowCreate true iff the caller is authorizing the issuance of a new identifier
+     * 
+     * @return object for the given inputs or null if none exists
+     * @throws IOException if an error occurs accessing the store
+     */
+    @Nullable PairwiseId getBySourceValue(@Nonnull final PairwiseId pid, final boolean allowCreate) throws IOException;
+       
+}
\ No newline at end of file
diff --git a/idp-attribute-impl/.checkstyle b/idp-attribute-impl/.checkstyle
new file mode 100644
index 0000000..5ef9989
--- /dev/null
+++ b/idp-attribute-impl/.checkstyle
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<fileset-config file-format-version="1.2.0" simple-config="false" sync-formatter="false">
+
+    <local-check-config name="Shibboleth Checkstyle" location="checkstyle.xml" type="project" description="">
+        <additional-data name="cache-file" value="true"/>
+        <additional-data name="cache-props-file-location" value="null_1312636288299_cache.properties"/>
+        <additional-data name="cache-file-location" value="null_1312636288299_cache.xml"/>
+    </local-check-config>
+  
+    <fileset name="main source" enabled="true" check-config-name="Shibboleth Checkstyle" local="true">
+        <file-match-pattern match-pattern="src/main/java/.*\.java$" include-pattern="true"/>
+    </fileset>
+  
+</fileset-config>
diff --git a/idp-attribute-impl/.classpath b/idp-attribute-impl/.classpath
new file mode 100644
index 0000000..4a3c7ea
--- /dev/null
+++ b/idp-attribute-impl/.classpath
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+			<attribute name="test" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+			<attribute name="test" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/idp-attribute-impl/.gitignore b/idp-attribute-impl/.gitignore
new file mode 100644
index 0000000..de41221
--- /dev/null
+++ b/idp-attribute-impl/.gitignore
@@ -0,0 +1,21 @@
+# A simulation of Subversion default ignores, generated by reposurgeon.
+*.o
+*.lo
+*.la
+*.al
+*.libs
+*.so
+*.so.[0-9]*
+*.a
+*.pyc
+*.pyo
+*.rej
+*.iml
+*~
+*.#*
+.*.swp
+.DS_store
+# Simulated Subversion default ignores end here
+
+/target
+/test-output
diff --git a/idp-attribute-impl/.project b/idp-attribute-impl/.project
new file mode 100644
index 0000000..93b9a0c
--- /dev/null
+++ b/idp-attribute-impl/.project
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>idp-attribute-impl</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>net.sf.eclipsecs.core.CheckstyleBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.springframework.ide.eclipse.core.springbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.springframework.ide.eclipse.boot.validation.springbootbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.m2e.core.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.springframework.ide.eclipse.core.springnature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>net.sf.eclipsecs.core.CheckstyleNature</nature>
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
+	</natures>
+</projectDescription>
diff --git a/idp-attribute-impl/.settings/org.eclipse.core.resources.prefs b/idp-attribute-impl/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..29abf99
--- /dev/null
+++ b/idp-attribute-impl/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,6 @@
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding//src/main/resources=UTF-8
+encoding//src/test/java=UTF-8
+encoding//src/test/resources=UTF-8
+encoding/<project>=UTF-8
diff --git a/idp-attribute-impl/.settings/org.eclipse.jdt.core.prefs b/idp-attribute-impl/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..4537d67
--- /dev/null
+++ b/idp-attribute-impl/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,351 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.builder.cleanOutputFolder=ignore
+org.eclipse.jdt.core.codeComplete.argumentPrefixes=
+org.eclipse.jdt.core.codeComplete.argumentSuffixes=
+org.eclipse.jdt.core.codeComplete.fieldPrefixes=
+org.eclipse.jdt.core.codeComplete.fieldSuffixes=
+org.eclipse.jdt.core.codeComplete.localPrefixes=
+org.eclipse.jdt.core.codeComplete.localSuffixes=
+org.eclipse.jdt.core.codeComplete.staticFieldPrefixes=
+org.eclipse.jdt.core.codeComplete.staticFieldSuffixes=
+org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes=
+org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes=
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=11
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nullReference=ignore
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=ignore
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.release=disabled
+org.eclipse.jdt.core.compiler.source=11
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=16
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=1
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=1
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
+org.eclipse.jdt.core.formatter.comment.line_length=120
+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=true
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=120
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_on_off_tags=false
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
diff --git a/idp-attribute-impl/.settings/org.eclipse.jdt.ui.prefs b/idp-attribute-impl/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..86c79c4
--- /dev/null
+++ b/idp-attribute-impl/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,63 @@
+cleanup.add_default_serial_version_id=false
+cleanup.add_generated_serial_version_id=true
+cleanup.add_missing_annotations=true
+cleanup.add_missing_deprecated_annotations=true
+cleanup.add_missing_methods=false
+cleanup.add_missing_nls_tags=false
+cleanup.add_missing_override_annotations=false
+cleanup.add_missing_override_annotations_interface_methods=true
+cleanup.add_serial_version_id=true
+cleanup.always_use_blocks=true
+cleanup.always_use_parentheses_in_expressions=false
+cleanup.always_use_this_for_non_static_field_access=false
+cleanup.always_use_this_for_non_static_method_access=false
+cleanup.convert_to_enhanced_for_loop=false
+cleanup.correct_indentation=true
+cleanup.format_source_code=true
+cleanup.format_source_code_changes_only=false
+cleanup.make_local_variable_final=true
+cleanup.make_parameters_final=true
+cleanup.make_private_fields_final=true
+cleanup.make_type_abstract_if_missing_method=false
+cleanup.make_variable_declarations_final=true
+cleanup.never_use_blocks=false
+cleanup.never_use_parentheses_in_expressions=true
+cleanup.organize_imports=true
+cleanup.qualify_static_field_accesses_with_declaring_class=false
+cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+cleanup.qualify_static_member_accesses_with_declaring_class=true
+cleanup.qualify_static_method_accesses_with_declaring_class=false
+cleanup.remove_private_constructors=true
+cleanup.remove_trailing_whitespaces=true
+cleanup.remove_trailing_whitespaces_all=true
+cleanup.remove_trailing_whitespaces_ignore_empty=false
+cleanup.remove_unnecessary_casts=true
+cleanup.remove_unnecessary_nls_tags=true
+cleanup.remove_unused_imports=true
+cleanup.remove_unused_local_variables=false
+cleanup.remove_unused_private_fields=true
+cleanup.remove_unused_private_members=false
+cleanup.remove_unused_private_methods=true
+cleanup.remove_unused_private_types=true
+cleanup.sort_members=false
+cleanup.sort_members_all=false
+cleanup.use_blocks=true
+cleanup.use_blocks_only_for_return_and_throw=false
+cleanup.use_parentheses_in_expressions=true
+cleanup.use_this_for_non_static_field_access=false
+cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+cleanup.use_this_for_non_static_method_access=false
+cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+cleanup_profile=_Shibboleth
+cleanup_settings_version=2
+eclipse.preferences.version=1
+formatter_profile=_Shibboleth
+formatter_settings_version=11
+internal.default.compliance=default
+org.eclipse.jdt.ui.exception.name=e
+org.eclipse.jdt.ui.gettersetter.use.is=true
+org.eclipse.jdt.ui.javadoc=true
+org.eclipse.jdt.ui.keywordthis=false
+org.eclipse.jdt.ui.overrideannotation=false
+org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates><template autoinsert\="true" context\="gettercomment_context" deleted\="false" description\="Comment for getter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.gettercomment" name\="gettercomment">/**\n * @return Returns the ${bare_field_name}.\n */</template><template autoinsert\="true" context\="settercomment_context" deleted\="false" description\="Commen [...]
diff --git a/idp-attribute-impl/.settings/org.eclipse.m2e.core.prefs b/idp-attribute-impl/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 0000000..f897a7f
--- /dev/null
+++ b/idp-attribute-impl/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/idp-attribute-impl/checkstyle.xml b/idp-attribute-impl/checkstyle.xml
new file mode 100644
index 0000000..48ae807
--- /dev/null
+++ b/idp-attribute-impl/checkstyle.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN" "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
+
+<!--
+    This configuration file was written by the eclipse-cs plugin configuration editor
+-->
+<!--
+    Checkstyle-Configuration: Shibboleth Checkstyle
+    Description: none
+-->
+<module name="Checker">
+  <property name="severity" value="warning"/>
+  <module name="TreeWalker">
+    <property name="tabWidth" value="4"/>
+    <module name="JavadocMethod">
+      <property name="allowThrowsTagsForSubclasses" value="true"/>
+      <property name="allowUndeclaredRTE" value="true"/>
+    </module>
+    <module name="JavadocType">
+        <property name="allowUnknownTags" value="true"/>
+    </module>
+    <module name="JavadocVariable"/>
+    <module name="JavadocStyle">
+      <property name="checkEmptyJavadoc" value="true"/>
+    </module>
+    <module name="ConstantName"/>
+    <module name="LocalFinalVariableName"/>
+    <module name="LocalVariableName"/>
+    <module name="MemberName"/>
+    <module name="MethodName"/>
+    <module name="PackageName"/>
+    <module name="ParameterName"/>
+    <module name="StaticVariableName"/>
+    <module name="TypeName"/>
+    <module name="AvoidStarImport"/>
+    <module name="IllegalImport"/>
+    <module name="RedundantImport"/>
+    <module name="UnusedImports"/>
+    <module name="LineLength">
+      <property name="max" value="120"/>
+    </module>
+    <module name="MethodLength">
+      <property name="max" value="70"/>
+    </module>
+    <module name="ParameterNumber">
+      <property name="max" value="5"/>
+    </module>
+    <module name="EmptyForIteratorPad"/>
+    <module name="MethodParamPad"/>
+    <module name="ModifierOrder"/>
+    <module name="AvoidNestedBlocks"/>
+    <module name="LeftCurly"/>
+    <module name="NeedBraces"/>
+    <module name="RightCurly"/>
+    <module name="EmptyStatement"/>
+    <module name="EqualsHashCode"/>
+    <module name="HiddenField"/>
+    <module name="IllegalInstantiation"/>
+    <module name="InnerAssignment"/>
+    <module name="MissingSwitchDefault"/>
+    <module name="SimplifyBooleanExpression"/>
+    <module name="SimplifyBooleanReturn"/>
+    <module name="FinalClass"/>
+    <module name="HideUtilityClassConstructor"/>
+    <module name="VisibilityModifier"/>
+    <module name="ArrayTypeStyle"/>
+    <module name="UpperEll"/>
+    <module name="AnonInnerLength"/>
+    <module name="EmptyForInitializerPad"/>
+    <module name="CovariantEquals"/>
+    <module name="DefaultComesLast"/>
+    <module name="DeclarationOrder"/>
+    <module name="ExplicitInitialization"/>
+    <module name="FallThrough"/>
+    <module name="IllegalThrows"/>
+    <module name="MultipleVariableDeclarations"/>
+    <module name="PackageDeclaration"/>
+    <module name="ParameterAssignment"/>
+    <module name="ReturnCount">
+      <property name="max" value="8"/>
+      <property name="maxForVoid" value="8"/>
+    </module>
+    <module name="StringLiteralEquality"/>
+    <module name="SuperFinalize"/>
+    <module name="ArrayTrailingComma"/>
+    <module name="UnnecessaryParentheses"/>
+    <module name="MutableException"/>
+    <module name="ThrowsCount">
+      <property name="max" value="3"/>
+    </module>
+    <module name="CyclomaticComplexity"/>
+    <module name="TrailingComment"/>
+    <module name="EqualsAvoidNull"/>
+    <module name="ModifiedControlVariable"/>
+    <module name="FinalParameters">
+      <property name="tokens" value="METHOD_DEF,CTOR_DEF,LITERAL_CATCH"/>
+    </module>
+    <module name="FinalLocalVariable">
+      <property name="tokens" value="PARAMETER_DEF,VARIABLE_DEF"/>
+      <property name="validateEnhancedForLoopVariable" value="true"/>
+    </module>
+    <module name="SuppressionCommentFilter">
+      <property name="offCommentFormat" value="\bCheck[Ss]tyle:\s*([\w|]+)\s+OFF\b"/>
+      <property name="onCommentFormat" value="\bCheck[Ss]tyle:\s*([\w|]+)\s+ON\b"/>
+      <property name="checkFormat" value="$1"/>
+    </module>
+  </module>
+  <module name="FileTabCharacter"/>
+  <module name="FileLength">
+    <property name="max" value="1000"/>
+  </module>
+  <module name="Header">
+    <property name="header" value="/*\n * Licensed to the University Corporation for Advanced Internet Development,\n * Inc. (UCAID) under one or more contributor license agreements.  See the\n * NOTICE file distributed with this work for additional information regarding\n * copyright ownership. The UCAID licenses this file to You under the Apache\n * License, Version 2.0 (the "License"); you may not use this file except in\n * compliance with the License.  You may obtain a cop [...]
+  </module>
+  <module name="JavadocPackage"/>
+</module>
diff --git a/idp-attribute-impl/pom.xml b/idp-attribute-impl/pom.xml
new file mode 100644
index 0000000..a5329d5
--- /dev/null
+++ b/idp-attribute-impl/pom.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>net.shibboleth.idp</groupId>
+        <artifactId>idp-parent</artifactId>
+        <version>4.0.0-SNAPSHOT</version>
+        <relativePath>../idp-parent</relativePath>
+    </parent>
+
+    <name>Shibboleth IdP :: Attribute Implementation</name>
+    <description>Attribute Implementation</description>
+    <artifactId>idp-attribute-impl</artifactId>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <!-- Compile Dependencies -->
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>idp-attribute-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <!-- Provided Dependencies -->
+
+        <!-- Runtime Dependencies -->
+
+        <!-- Test Dependencies -->
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>idp-core</artifactId>
+            <version>${project.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hsqldb</groupId>
+            <artifactId>hsqldb</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- Managed Dependencies -->
+    </dependencies>
+    
+</project>
diff --git a/idp-attribute-impl/src/main/java/.gitkeep b/idp-attribute-impl/src/main/java/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/ComputedPersistentIdGenerationStrategy.java b/idp-attribute-impl/src/main/java/net/shibboleth/idp/attribute/impl/ComputedPairwiseIdStore.java
similarity index 80%
rename from idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/ComputedPersistentIdGenerationStrategy.java
rename to idp-attribute-impl/src/main/java/net/shibboleth/idp/attribute/impl/ComputedPairwiseIdStore.java
index cb0dc4c..75c32e2 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/ComputedPersistentIdGenerationStrategy.java
+++ b/idp-attribute-impl/src/main/java/net/shibboleth/idp/attribute/impl/ComputedPairwiseIdStore.java
@@ -15,8 +15,9 @@
  * limitations under the License.
  */
 
-package net.shibboleth.idp.saml.nameid.impl;
+package net.shibboleth.idp.attribute.impl;
 
+import java.io.IOException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Collections;
@@ -26,6 +27,8 @@ import java.util.Map;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
+import net.shibboleth.idp.attribute.PairwiseId;
+import net.shibboleth.idp.attribute.PairwiseIdStore;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.codec.Base32Support;
@@ -36,26 +39,26 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.primitive.StringSupport;
 
-import org.opensaml.saml.common.SAMLException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * The basis of a {@link PersistentIdGenerationStrategy} that generates a unique ID by computing the hash of
- * a given attribute value, the entity ID of the inbound message issuer, and a provided salt.
+ * A {@link PairwiseIdStore} that generates a pairwise ID by computing the hash of
+ * a given attribute value, the entity ID of the recipient, and a provided salt.
  * 
  * <p>The original implementation and values in common use relied on base64 encoding of the result,
  * but due to discovery of the lack of appropriate case handling of identifiers by applications, the
- * ability to use base32 has been added to eliminate the possibility of case conflicts.</p> 
+ * ability to use base32 has been added to eliminate the possibility of case conflicts.</p>
+ * 
+ * @since 4.0.0
  */
-public class ComputedPersistentIdGenerationStrategy extends AbstractInitializableComponent
-        implements PersistentIdGenerationStrategy {
+public class ComputedPairwiseIdStore extends AbstractInitializableComponent implements PairwiseIdStore {
 
     /** An override trigger to apply to all relying parties. */
     @Nonnull @NotEmpty public static final String WILDCARD_OVERRIDE = "*";
     
     /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(ComputedPersistentIdGenerationStrategy.class);
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ComputedPairwiseIdStore.class);
 
     /** Post-digest encoding types. */
     public enum Encoding {
@@ -79,7 +82,7 @@ public class ComputedPersistentIdGenerationStrategy extends AbstractInitializabl
     @Nonnull private Map<String,Map<String,String>> exceptionMap;
     
     /** Constructor. */
-    public ComputedPersistentIdGenerationStrategy() {
+    public ComputedPairwiseIdStore() {
         algorithm = "SHA";
         encoding = Encoding.BASE64;
         exceptionMap = Collections.emptyMap();
@@ -110,13 +113,26 @@ public class ComputedPersistentIdGenerationStrategy extends AbstractInitializabl
     }
     
     /**
-     * Set the base64-encoded salt used when computing the ID.
+     * Set the salt used when computing the ID.
      * 
      * <p>An empty/null input is ignored.</p>
      * 
      * @param newValue used when computing the ID
+     */
+    public void setSalt(@Nullable final String newValue) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        if (newValue != null && !newValue.isEmpty()) {
+            salt = newValue.getBytes();
+        }
+    }
+    
+    /**
+     * Set the base64-encoded salt used when computing the ID.
+     * 
+     * <p>An empty/null input is ignored.</p>
      * 
-     * @since 3.3.0
+     * @param newValue used when computing the ID
      */
     public void setEncodedSalt(@Nullable final String newValue) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
@@ -130,8 +146,6 @@ public class ComputedPersistentIdGenerationStrategy extends AbstractInitializabl
      * Get the JCE algorithm name of the digest algorithm to use (default is SHA).
      * 
      * @return JCE message digest algorithm
-     * 
-     * @since 3.4.0
      */
     @Nonnull @NotEmpty public String getAlgorithm() {
         return algorithm;
@@ -152,8 +166,6 @@ public class ComputedPersistentIdGenerationStrategy extends AbstractInitializabl
      * Get the post-digest encoding to use.
      * 
      * @return encoding
-     * 
-     * @since 3.4.0
      */
     @Nonnull public Encoding getEncoding() {
         return encoding;
@@ -217,37 +229,42 @@ public class ComputedPersistentIdGenerationStrategy extends AbstractInitializabl
             throw new ComponentInitializationException("Salt must be at least 16 bytes in size");
         }
     }
-    
+
     /** {@inheritDoc} */
-    @Override
-    @Nonnull @NotEmpty public String generate(@Nonnull @NotEmpty final String assertingPartyId,
-            @Nonnull @NotEmpty final String relyingPartyId, @Nonnull @NotEmpty final String principalName,
-            @Nonnull @NotEmpty final String sourceId) throws SAMLException {
+    @Nullable public PairwiseId getBySourceValue(@Nonnull final PairwiseId pid, final boolean allowCreate)
+            throws IOException {
         ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
-     
-        final byte[] effectiveSalt = getEffectiveSalt(principalName, relyingPartyId);
+        Constraint.isNotNull(pid, "Input PairwiseId object cannot be null");
+        Constraint.isNotEmpty(pid.getRecipientEntityID(), "Recipient entityID cannot be null or empty");
+        Constraint.isNotEmpty(pid.getPrincipalName(), "Principal name cannot be null or empty");
+        Constraint.isNotEmpty(pid.getSourceSystemId(), "Source system ID cannot be null or empty");
+    
+        final byte[] effectiveSalt = getEffectiveSalt(pid.getPrincipalName(), pid.getRecipientEntityID());
         if (effectiveSalt == null) {
-            throw new SAMLException("Generation blocked by exception rule");
+            log.warn("Pairwise ID generation blocked for relying party ({})", pid.getRecipientEntityID());
+            throw new IOException("Pairwise ID generation blocked by exception rule");
         }
         
         try {
             final MessageDigest md = MessageDigest.getInstance(algorithm);
-            md.update(relyingPartyId.getBytes());
+            md.update(pid.getRecipientEntityID().getBytes());
             md.update((byte) '!');
-            md.update(sourceId.getBytes());
+            md.update(pid.getSourceSystemId().getBytes());
             md.update((byte) '!');
 
             if (encoding == Encoding.BASE32) {
-                return Base32Support.encode(md.digest(effectiveSalt), Base32Support.UNCHUNKED);
+                pid.setPairwiseId(Base32Support.encode(md.digest(effectiveSalt), Base32Support.UNCHUNKED));
             } else if (encoding == Encoding.BASE64) {
-                return Base64Support.encode(md.digest(effectiveSalt), Base64Support.UNCHUNKED);
+                pid.setPairwiseId(Base64Support.encode(md.digest(effectiveSalt), Base64Support.UNCHUNKED));
             } else {
-                throw new SAMLException("Desired encoding was not recognized, unable to compute ID");
+                throw new IOException("Desired encoding was not recognized, unable to compute ID");
             }
         } catch (final NoSuchAlgorithmException e) {
             log.error("Digest algorithm {} is not supported", algorithm);
-            throw new SAMLException("Digest algorithm was not supported, unable to compute ID", e);
+            throw new IOException("Digest algorithm was not supported, unable to compute ID", e);
         }
+        
+        return pid;
     }
     
     /**
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/JDBCPersistentIdStoreEx.java b/idp-attribute-impl/src/main/java/net/shibboleth/idp/attribute/impl/JDBCPairwiseIdStore.java
similarity index 67%
rename from idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/JDBCPersistentIdStoreEx.java
rename to idp-attribute-impl/src/main/java/net/shibboleth/idp/attribute/impl/JDBCPairwiseIdStore.java
index c170133..dd44c7f 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/JDBCPersistentIdStoreEx.java
+++ b/idp-attribute-impl/src/main/java/net/shibboleth/idp/attribute/impl/JDBCPairwiseIdStore.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package net.shibboleth.idp.saml.nameid.impl;
+package net.shibboleth.idp.attribute.impl;
 
 import java.io.IOException;
 import java.sql.Connection;
@@ -34,7 +34,9 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.sql.DataSource;
 
-import net.shibboleth.idp.saml.nameid.PersistentIdEntry;
+import net.shibboleth.idp.attribute.DurablePairwiseIdStore;
+import net.shibboleth.idp.attribute.PairwiseId;
+import net.shibboleth.idp.attribute.PairwiseIdStore;
 import net.shibboleth.utilities.java.support.annotation.Duration;
 import net.shibboleth.utilities.java.support.annotation.constraint.Live;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonNegative;
@@ -47,15 +49,13 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.primitive.StringSupport;
 
-import org.joda.time.DateTime;
-import org.opensaml.saml.common.SAMLException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * JDBC-based storage management for SAML persistent IDs.
+ * JDBC-based storage management for pairwise IDs.
  * 
- * <p>The general DDL for the database is:
+ * <p>The general DDL for the database, which is unchanged for compatibility, is:</p>
  * 
  * <pre>
  * CREATE TABLE shibpid (
@@ -70,13 +70,15 @@ import org.slf4j.LoggerFactory;
  *      PRIMARY KEY (localEntity, peerEntity, persistentId)
  *     );</pre>.
  *    
- * The first three columns should be defined as the primary key of the table, and the other columns
+ * <p>The first three columns should be defined as the primary key of the table, and the other columns
  * should be indexed.</p>
+ * 
+ * @since 4.0.0
  */
-public class JDBCPersistentIdStoreEx extends AbstractInitializableComponent implements PersistentIdStoreEx {
+public class JDBCPairwiseIdStore extends AbstractInitializableComponent implements DurablePairwiseIdStore {
 
     /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(JDBCPersistentIdStoreEx.class);
+    @Nonnull private final Logger log = LoggerFactory.getLogger(JDBCPairwiseIdStore.class);
     
     /** JDBC data source for retrieving connections. */
     @NonnullAfterInit private DataSource dataSource;
@@ -137,9 +139,12 @@ public class JDBCPersistentIdStoreEx extends AbstractInitializableComponent impl
     
     /** Parameterized delete statement used to clear dummy rows after verification. */
     @NonnullAfterInit private String deleteSQL;
+    
+    /** Optional hook for obtaining initial values from a primary store, usually a computed algorithm. */
+    @Nullable private PairwiseIdStore initialValueStore;
 
     /** Constructor. */
-    public JDBCPersistentIdStoreEx() {
+    public JDBCPairwiseIdStore() {
         transactionRetry = 3;
         retryableErrors = Arrays.asList("23000", "23505");
         queryTimeout = 5000;
@@ -437,6 +442,29 @@ public class JDBCPersistentIdStoreEx extends AbstractInitializableComponent impl
         
         deleteSQL = Constraint.isNotNull(StringSupport.trimOrNull(sql), "SQL statement cannot be null or empty");
     }
+        
+    /**
+     * Get a store to use to produce the first value for a given issuer/recipient pair.
+     * 
+     * @return initial value source
+     */
+    @Nullable public PairwiseIdStore getInitialValueStore() {
+        return initialValueStore;
+    }
+
+    /**
+     * Set a store to use to produce the first value for a given issuer/recipient pair.
+     * 
+     * <p>This is typically used to draw the "first" (often only) value for a given pairwise
+     * relationship from an algorithm instead of a random value requiring storage to know.</p>
+     * 
+     * @param store initial value source
+     */
+    public void setInitialValueStore(@Nullable final PairwiseIdStore store) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        initialValueStore = store;
+    }
     
     /** {@inheritDoc} */
     @Override
@@ -444,7 +472,7 @@ public class JDBCPersistentIdStoreEx extends AbstractInitializableComponent impl
         super.doInitialize();
         
         if (null == dataSource) {
-            throw new ComponentInitializationException(getLogPrefix() + " No database connection provided");
+            throw new ComponentInitializationException("DataSource cannot be null");
         }
         
         if (getByIssuedSelectSQL == null) {
@@ -492,181 +520,179 @@ public class JDBCPersistentIdStoreEx extends AbstractInitializableComponent impl
         
         try {
             verifyDatabase();
-            log.info("{} Data source successfully verified", getLogPrefix());
+            log.info("DataSource successfully verified");
         } catch (final SQLException e) {
             if (verifyDatabase) {
-                log.error("{} Exception verifying database", getLogPrefix(), e);
+                log.error("Exception verifying database", e);
                 throw new ComponentInitializationException(
                         "The database was not reachable or was not defined with an appropriate table + primary key");
             } else {
-                log.warn("{} The database was not reachable or was not defined with an appropriate table + primary key",
-                        getLogPrefix(), e);
-            }
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    @Nullable public PersistentIdEntry getByIssuedValue(@Nonnull @NotEmpty final String nameQualifier,
-            @Nonnull @NotEmpty final String spNameQualifier, @Nonnull @NotEmpty final String persistentId)
-                    throws IOException {
-        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
-
-        log.debug("{} Selecting previously issued persistent ID entry", getLogPrefix(), getByIssuedSelectSQL);
-
-        log.trace("{} Prepared statement: {}", getLogPrefix(), getByIssuedSelectSQL);
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 1, nameQualifier);
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 2, spNameQualifier);
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 3, persistentId);
-
-        try (final Connection dbConn = getConnection(true)) {
-            final PreparedStatement statement = dbConn.prepareStatement(getByIssuedSelectSQL);
-            statement.setQueryTimeout((int) (queryTimeout / 1000));
-
-            statement.setString(1, nameQualifier);
-            statement.setString(2, spNameQualifier);
-            statement.setString(3, persistentId);
-
-            final List<PersistentIdEntry> entries = buildIdentifierEntries(statement.executeQuery());
-
-            if (entries == null || entries.size() == 0) {
-                return null;
+                log.warn("The database was not reachable or was not defined with an appropriate table + primary key",e);
             }
-
-            if (entries.size() > 1) {
-                log.warn("{} More than one record found, only the first will be returned", getLogPrefix());
-            }
-
-            return entries.get(0);
-        } catch (final SQLException e) {
-            throw new IOException(e);
         }
     }
 
-// Checkstyle: MethodLength|CyclomaticComplexity|ParameterNumber OFF
+    // Checkstyle: MethodLength|CyclomaticComplexity OFF
     /** {@inheritDoc} */
-    @Override
-    @Nullable public PersistentIdEntry getBySourceValue(@Nonnull @NotEmpty final String nameQualifier,
-            @Nonnull @NotEmpty final String spNameQualifier, @Nonnull @NotEmpty final String sourceId,
-            @Nonnull @NotEmpty final String principal, final boolean allowCreate,
-            @Nullable final ComputedPersistentIdGenerationStrategy computedIdStrategy) throws IOException {
+    @Nullable public PairwiseId getBySourceValue(@Nonnull final PairwiseId pid, final boolean allowCreate)
+            throws IOException {
         ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
-        
-        log.debug("{} Obtaining persistent ID for source ID: {}", getLogPrefix(), sourceId);
-
-        log.trace("{} Prepared statement: {}", getLogPrefix(), getBySourceSelectSQL);
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 1, nameQualifier);
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 2, spNameQualifier);
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 3, sourceId);
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 4, nameQualifier);
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 5, spNameQualifier);
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 6, sourceId);
+        Constraint.isNotNull(pid, "Input PairwiseId object cannot be null");
+        Constraint.isNotEmpty(pid.getIssuerEntityID(), "Issuer entityID cannot be null or empty");
+        Constraint.isNotEmpty(pid.getRecipientEntityID(), "Recipient entityID cannot be null or empty");
+        Constraint.isNotEmpty(pid.getPrincipalName(), "Principal name cannot be null or empty");
+        Constraint.isNotEmpty(pid.getSourceSystemId(), "Source system ID cannot be null or empty");
+        
+        log.debug("Obtaining pairwise ID for source ID: {}", pid.getSourceSystemId());
+
+        log.trace("Prepared statement: {}", getBySourceSelectSQL);
+        log.trace("Setting prepared statement parameter {}: {}", 1, pid.getIssuerEntityID());
+        log.trace("Setting prepared statement parameter {}: {}", 2, pid.getRecipientEntityID());
+        log.trace("Setting prepared statement parameter {}: {}", 3, pid.getSourceSystemId());
+        log.trace("Setting prepared statement parameter {}: {}", 4, pid.getIssuerEntityID());
+        log.trace("Setting prepared statement parameter {}: {}", 5, pid.getRecipientEntityID());
+        log.trace("Setting prepared statement parameter {}: {}", 6, pid.getSourceSystemId());
 
         int retries = transactionRetry;
         while (true) {
             try (final Connection dbConn = getConnection(false)) {
                 final PreparedStatement statement = dbConn.prepareStatement(getBySourceSelectSQL);
                 statement.setQueryTimeout((int) (queryTimeout / 1000));
-                statement.setString(1, nameQualifier);
-                statement.setString(2, spNameQualifier);
-                statement.setString(3, sourceId);
-                statement.setString(4, nameQualifier);
-                statement.setString(5, spNameQualifier);
-                statement.setString(6, sourceId);
-        
-                log.debug("{} Getting active and/or last inactive persistent Id entry", getLogPrefix());
-                final List<PersistentIdEntry> entries = buildIdentifierEntries(statement.executeQuery());
+                statement.setString(1, pid.getIssuerEntityID());
+                statement.setString(2, pid.getRecipientEntityID());
+                statement.setString(3, pid.getSourceSystemId());
+                statement.setString(4, pid.getIssuerEntityID());
+                statement.setString(5, pid.getRecipientEntityID());
+                statement.setString(6, pid.getSourceSystemId());
+        
+                log.debug("Getting active and/or last inactive pairwise ID entry");
+                final List<PairwiseId> entries = buildIdentifierEntries(statement.executeQuery());
                 if (entries != null && entries.size() > 0 && (entries.get(0).getDeactivationTime() == null
-                        || entries.get(0).getDeactivationTime().getTime() > System.currentTimeMillis())) {
-                    log.debug("{} Returning existing active persistent ID: {}", getLogPrefix(),
-                            entries.get(0).getPersistentId());
+                        || entries.get(0).getDeactivationTime() > System.currentTimeMillis())) {
                     dbConn.commit();
+                    log.debug("Returning existing active pairwise ID: {}", entries.get(0).getPairwiseId());
                     return entries.get(0);
                 } else if (!allowCreate) {
-                    log.debug("{} No existing persistent ID and creation is not permitted", getLogPrefix());
                     dbConn.commit();
+                    log.debug("No existing pairwise ID and creation is not permitted by caller");
                     return null;
                 }
 
-                final PersistentIdEntry newEntry = new PersistentIdEntry();
-                newEntry.setIssuerEntityId(nameQualifier);
-                newEntry.setRecipientEntityId(spNameQualifier);
-                newEntry.setSourceId(sourceId);
-                newEntry.setPrincipalName(principal);
-                newEntry.setCreationTime(new Timestamp(System.currentTimeMillis()));
-
-                if ((entries == null || entries.size() == 0) && computedIdStrategy != null) {
-                    log.debug("{} Issuing new computed persistent ID", getLogPrefix());
-                    newEntry.setPersistentId(
-                            computedIdStrategy.generate(nameQualifier, spNameQualifier, principal, sourceId));
+                pid.setCreationTime(System.currentTimeMillis());
+                
+                // Circumvent final modifier on parameter.
+                PairwiseId retValue = pid;
+
+                if ((entries == null || entries.size() == 0) && initialValueStore != null) {
+                    log.debug("Issuing new pairwise ID using initial value store");
+                    retValue = initialValueStore.getBySourceValue(pid, allowCreate);
                 } else {
-                    log.debug("{} Issuing new random persistent ID", getLogPrefix());
-                    newEntry.setPersistentId(UUID.randomUUID().toString());
+                    log.debug("Issuing new random pairwise ID");
+                    retValue.setPairwiseId(UUID.randomUUID().toString());
                     if (entries != null && entries.size() > 0) {
-                        newEntry.setPeerProvidedId(entries.get(0).getPeerProvidedId());
+                        retValue.setPeerProvidedId(entries.get(0).getPeerProvidedId());
                     }
                 }
-                store(newEntry, dbConn);
+                store(retValue, dbConn);
                 dbConn.commit();
-                return newEntry;
+                return retValue;
             } catch (final SQLException e) {
                 boolean retry = false;
                 for (final String msg : retryableErrors) {
                     if (e.getSQLState() != null && e.getSQLState().contains(msg)) {
-                        log.warn("{} Caught retryable SQL exception", getLogPrefix(), e);
+                        log.warn("Caught retryable SQL exception", e);
                         retry = true;
+                        break;
                     }
                 }
                 
                 if (retry) {
                     if (--retries < 0) {
-                        log.warn("{} Error retryable, but retry limit exceeded", getLogPrefix());
+                        log.warn("Error retryable, but retry limit exceeded");
                         throw new IOException(e);
                     } else {
-                        log.info("{} Retrying persistent ID lookup/create operation", getLogPrefix());
+                        log.info("Retrying pairwise ID lookup/create operation");
                     }
                 } else {
                     throw new IOException(e);
                 }
-            } catch (final SAMLException e) {
-                throw new IOException(e);
             }
         }
     }
-// Checkstyle: MethodLength|CyclomaticComplexity|ParameterNumber ON
+// Checkstyle: MethodLength|CyclomaticComplexity ON
     
     /** {@inheritDoc} */
-    @Override
-    public void deactivate(@Nonnull @NotEmpty final String nameQualifier,
-            @Nonnull @NotEmpty final String spNameQualifier, @Nonnull @NotEmpty final String persistentId,
-            @Nullable final DateTime deactivation) throws IOException {
+    @Nullable public PairwiseId getByIssuedValue(@Nonnull final PairwiseId pid) throws IOException {
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+        Constraint.isNotNull(pid, "Input PairwiseId object cannot be null");
+        Constraint.isNotEmpty(pid.getIssuerEntityID(), "Issuer entityID cannot be null or empty");
+        Constraint.isNotEmpty(pid.getRecipientEntityID(), "Recipient entityID cannot be null or empty");
+        Constraint.isNotEmpty(pid.getPairwiseId(), "Pairwise ID cannot be null or empty");
+    
+        log.debug("Selecting previously issued pairwise ID entry", getByIssuedSelectSQL);
+    
+        log.trace("Prepared statement: {}", getByIssuedSelectSQL);
+        log.trace("Setting prepared statement parameter {}: {}", 1, pid.getIssuerEntityID());
+        log.trace("Setting prepared statement parameter {}: {}", 2, pid.getRecipientEntityID());
+        log.trace("Setting prepared statement parameter {}: {}", 3, pid.getPairwiseId());
+    
+        try (final Connection dbConn = getConnection(true)) {
+            final PreparedStatement statement = dbConn.prepareStatement(getByIssuedSelectSQL);
+            statement.setQueryTimeout((int) (queryTimeout / 1000));
+    
+            statement.setString(1, pid.getIssuerEntityID());
+            statement.setString(2, pid.getRecipientEntityID());
+            statement.setString(3, pid.getPairwiseId());
+    
+            final List<PairwiseId> entries = buildIdentifierEntries(statement.executeQuery());
+    
+            if (entries == null || entries.size() == 0) {
+                return null;
+            }
+    
+            if (entries.size() > 1) {
+                log.warn("More than one record found, only the first will be returned");
+            }
+    
+            return entries.get(0);
+        } catch (final SQLException e) {
+            throw new IOException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void deactivate(@Nonnull final PairwiseId pid) throws IOException {
         ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+        Constraint.isNotNull(pid, "Input PairwiseId object cannot be null");
+        Constraint.isNotEmpty(pid.getIssuerEntityID(), "Issuer entityID cannot be null or empty");
+        Constraint.isNotEmpty(pid.getRecipientEntityID(), "Recipient entityID cannot be null or empty");
+        Constraint.isNotEmpty(pid.getPairwiseId(), "Pairwise ID cannot be null or empty");
         
         final Timestamp deactivationTime;
-        if (deactivation == null) {
+        if (pid.getDeactivationTime() == null) {
             deactivationTime = new Timestamp(System.currentTimeMillis());
         } else {
-            deactivationTime = new Timestamp(deactivation.getMillis());
+            deactivationTime = new Timestamp(pid.getDeactivationTime());
         }
 
-        log.debug("Deactivating persistent id {} as of {}", persistentId, deactivationTime);
+        log.debug("Deactivating pairwise ID {} as of {}", pid.getPairwiseId(), deactivationTime);
 
-        log.trace("{} Prepared statement: {}", getLogPrefix(), deactivateSQL);
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 1, deactivationTime);
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 2, nameQualifier);
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 3, spNameQualifier);
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 4, persistentId);
+        log.trace("Prepared statement: {}", deactivateSQL);
+        log.trace("Setting prepared statement parameter {}: {}", 1, deactivationTime);
+        log.trace("Setting prepared statement parameter {}: {}", 2, pid.getIssuerEntityID());
+        log.trace("Setting prepared statement parameter {}: {}", 3, pid.getRecipientEntityID());
+        log.trace("Setting prepared statement parameter {}: {}", 4, pid.getPairwiseId());
         
         try (final Connection dbConn = getConnection(true)) {
             final PreparedStatement statement = dbConn.prepareStatement(deactivateSQL);
             statement.setQueryTimeout((int) (queryTimeout / 1000));
             statement.setTimestamp(1, deactivationTime);
-            statement.setString(2, nameQualifier);
-            statement.setString(3, spNameQualifier);
-            statement.setString(4, persistentId);
+            statement.setString(2, pid.getIssuerEntityID());
+            statement.setString(3, pid.getRecipientEntityID());
+            statement.setString(4, pid.getPairwiseId());
             final int rowCount = statement.executeUpdate();
             if (rowCount != 1) {
-                log.warn("{} Unexpected result, statement affected {} rows", getLogPrefix(), rowCount);
+                log.warn("Unexpected result, statement affected {} rows", rowCount);
             }
             
         } catch (final SQLException e) {
@@ -675,37 +701,39 @@ public class JDBCPersistentIdStoreEx extends AbstractInitializableComponent impl
     }
 
     /** {@inheritDoc} */
-    @Override
-    public void attach(@Nonnull @NotEmpty final String nameQualifier, @Nonnull @NotEmpty final String spNameQualifier,
-            @Nonnull @NotEmpty final String persistentId, @Nonnull @NotEmpty final String spProvidedId)
-            throws IOException {
+    public void attach(@Nonnull final PairwiseId pid) throws IOException {
         ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+        Constraint.isNotNull(pid, "Input PairwiseId object cannot be null");
+        Constraint.isNotEmpty(pid.getIssuerEntityID(), "Issuer entityID cannot be null or empty");
+        Constraint.isNotEmpty(pid.getRecipientEntityID(), "Recipient entityID cannot be null or empty");
+        Constraint.isNotEmpty(pid.getPairwiseId(), "Pairwise ID cannot be null or empty");
+        Constraint.isNotEmpty(pid.getPeerProvidedId(), "Peer-provided ID cannot be null or empty");
 
-        log.debug("Attaching SPProvidedID {} to persistent id {}", spProvidedId, persistentId);
+        log.debug("Attaching peer-provided ID {} to pairwise id {}", pid.getPeerProvidedId(), pid.getPairwiseId());
 
-        log.trace("{} Prepared statement: {}", getLogPrefix(), attachSQL);
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 1, spProvidedId);
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 2, nameQualifier);
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 3, spNameQualifier);
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 4, persistentId);
+        log.trace("Prepared statement: {}", attachSQL);
+        log.trace("Setting prepared statement parameter {}: {}", 1, pid.getPeerProvidedId());
+        log.trace("Setting prepared statement parameter {}: {}", 2, pid.getIssuerEntityID());
+        log.trace("Setting prepared statement parameter {}: {}", 3, pid.getRecipientEntityID());
+        log.trace("Setting prepared statement parameter {}: {}", 4, pid.getPairwiseId());
         
         try (final Connection dbConn = getConnection(true)) {
             final PreparedStatement statement = dbConn.prepareStatement(attachSQL);
             statement.setQueryTimeout((int) (queryTimeout / 1000));
-            statement.setString(1, spProvidedId);
-            statement.setString(2, nameQualifier);
-            statement.setString(3, spNameQualifier);
-            statement.setString(4, persistentId);
+            statement.setString(1, pid.getPeerProvidedId());
+            statement.setString(2, pid.getIssuerEntityID());
+            statement.setString(3, pid.getRecipientEntityID());
+            statement.setString(4, pid.getPairwiseId());
             final int rowCount = statement.executeUpdate();
             if (rowCount != 1) {
-                log.warn("{} Unexpected result, statement affected {} rows", getLogPrefix(), rowCount);
+                log.warn("Unexpected result, statement affected {} rows", rowCount);
             }
         } catch (final SQLException e) {
             throw new IOException(e);
         }
     }
     
-// Checkstyle: MethodLength|CyclomaticComplexity|ParameterNumber ON
+// Checkstyle: MethodLength|CyclomaticComplexity ON
     
     /**
      * Store a record containing the values from the input object.
@@ -715,45 +743,45 @@ public class JDBCPersistentIdStoreEx extends AbstractInitializableComponent impl
      * 
      * @throws SQLException if an error occurs
      */
-    void store(@Nonnull final PersistentIdEntry entry, @Nonnull final Connection dbConn) throws SQLException {
+    void store(@Nonnull final PairwiseId entry, @Nonnull final Connection dbConn) throws SQLException {
         
-        log.debug("{} Storing new persistent ID entry", getLogPrefix());
+        log.debug("Storing new pairwise ID entry");
         
-        if (StringSupport.trimOrNull(entry.getIssuerEntityId()) == null
-                || StringSupport.trimOrNull(entry.getRecipientEntityId()) == null
-                || StringSupport.trimOrNull(entry.getPersistentId()) == null
+        if (StringSupport.trimOrNull(entry.getIssuerEntityID()) == null
+                || StringSupport.trimOrNull(entry.getRecipientEntityID()) == null
+                || StringSupport.trimOrNull(entry.getPairwiseId()) == null
                 || StringSupport.trimOrNull(entry.getPrincipalName()) == null
-                || StringSupport.trimOrNull(entry.getSourceId()) == null
+                || StringSupport.trimOrNull(entry.getSourceSystemId()) == null
                 || entry.getCreationTime() == null) {
             throw new SQLException("Required field was empty/null, store operation not possible");
         }
         
-        log.trace("{} Prepared statement: {}", getLogPrefix(), insertSQL);
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 1, entry.getIssuerEntityId());
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 2, entry.getRecipientEntityId());
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 3, entry.getPersistentId());
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 4, entry.getPrincipalName());
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 5, entry.getSourceId());
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 6, entry.getPeerProvidedId());
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 7, entry.getCreationTime());
-        log.trace("{} Setting prepared statement parameter {}: {}", getLogPrefix(), 8, entry.getDeactivationTime());
+        log.trace("Prepared statement: {}", insertSQL);
+        log.trace("Setting prepared statement parameter {}: {}", 1, entry.getIssuerEntityID());
+        log.trace("Setting prepared statement parameter {}: {}", 2, entry.getRecipientEntityID());
+        log.trace("Setting prepared statement parameter {}: {}", 3, entry.getPairwiseId());
+        log.trace("Setting prepared statement parameter {}: {}", 4, entry.getPrincipalName());
+        log.trace("Setting prepared statement parameter {}: {}", 5, entry.getSourceSystemId());
+        log.trace("Setting prepared statement parameter {}: {}", 6, entry.getPeerProvidedId());
+        log.trace("Setting prepared statement parameter {}: {}", 7, entry.getCreationTime());
+        log.trace("Setting prepared statement parameter {}: {}", 8, entry.getDeactivationTime());
         
         final PreparedStatement statement = dbConn.prepareStatement(insertSQL);
         statement.setQueryTimeout((int) (queryTimeout / 1000));
     
-        statement.setString(1, entry.getIssuerEntityId());
-        statement.setString(2, entry.getRecipientEntityId());
-        statement.setString(3, entry.getPersistentId());
+        statement.setString(1, entry.getIssuerEntityID());
+        statement.setString(2, entry.getRecipientEntityID());
+        statement.setString(3, entry.getPairwiseId());
         statement.setString(4, entry.getPrincipalName());
-        statement.setString(5, entry.getSourceId());
+        statement.setString(5, entry.getSourceSystemId());
         if (entry.getPeerProvidedId() != null) {
             statement.setString(6, entry.getPeerProvidedId());
         } else {
             statement.setNull(6, Types.VARCHAR);
         }
-        statement.setTimestamp(7, entry.getCreationTime());
+        statement.setTimestamp(7, new Timestamp(entry.getCreationTime()));
         if (entry.getDeactivationTime() != null) {
-            statement.setTimestamp(8, entry.getDeactivationTime());
+            statement.setTimestamp(8, new Timestamp(entry.getDeactivationTime()));
         } else {
             statement.setNull(8, Types.TIMESTAMP);
         }
@@ -787,18 +815,16 @@ public class JDBCPersistentIdStoreEx extends AbstractInitializableComponent impl
         
         final String uuid = UUID.randomUUID().toString();
         
-        final PersistentIdEntry newEntry = new PersistentIdEntry();
-        newEntry.setIssuerEntityId("http://dummy.com/idp/" + uuid);
-        newEntry.setRecipientEntityId("http://dummy.com/sp/" + uuid);
-        newEntry.setSourceId("dummy");
+        final PairwiseId newEntry = new PairwiseId();
+        newEntry.setIssuerEntityID("http://dummy.com/idp/" + uuid);
+        newEntry.setRecipientEntityID("http://dummy.com/sp/" + uuid);
+        newEntry.setSourceSystemId("dummy");
         newEntry.setPrincipalName("dummy");
-        newEntry.setCreationTime(new Timestamp(System.currentTimeMillis()));
-        newEntry.setPersistentId(uuid);
+        newEntry.setCreationTime(System.currentTimeMillis());
+        newEntry.setPairwiseId(uuid);
         
         try (final Connection conn = getConnection(true)) {
             store(newEntry, conn);
-        } finally {
-            
         }
 
         boolean keyMissing = false;
@@ -807,11 +833,9 @@ public class JDBCPersistentIdStoreEx extends AbstractInitializableComponent impl
             keyMissing = true;
         } catch (final SQLException e) {
             if (e.getSQLState() != null && !retryableErrors.contains(e.getSQLState())) {
-                log.warn("{} Duplicate insert failed as required with SQL State '{}', ensure this value is "
-                        + "configured as a retryable error", getLogPrefix(), e.getSQLState());
+                log.warn("Duplicate insert failed as required with SQL State '{}', ensure this value is "
+                        + "configured as a retryable error", e.getSQLState());
             }
-        } finally {
-            
         }
 
         try (final Connection conn = getConnection(true)) {
@@ -819,8 +843,6 @@ public class JDBCPersistentIdStoreEx extends AbstractInitializableComponent impl
             statement.setQueryTimeout((int) (queryTimeout / 1000));
             statement.setString(1, "http://dummy.com/idp/" + uuid);
             statement.executeUpdate();
-        } finally {
-            
         }
         
         if (keyMissing) {
@@ -829,43 +851,41 @@ public class JDBCPersistentIdStoreEx extends AbstractInitializableComponent impl
     }
     
     /**
-     * Builds a list of {@link PersistentIdEntry}s from a result set.
+     * Build a list of {@link PairwiseId} objects from a result set.
      * 
      * @param resultSet the result set
      * 
-     * @return list of {@link PersistentIdEntry}s
+     * @return list of {@link PairwiseId} objects
      * 
      * @throws SQLException thrown if there is a problem reading the information from the database
      */
-    @Nonnull @NonnullElements @Live private List<PersistentIdEntry> buildIdentifierEntries(
+    @Nonnull @NonnullElements @Live private List<PairwiseId> buildIdentifierEntries(
             @Nonnull final ResultSet resultSet) throws SQLException {
-        final ArrayList<PersistentIdEntry> entries = new ArrayList<>();
+        
+        final ArrayList<PairwiseId> entries = new ArrayList<>();
     
         while (resultSet.next()) {
-            final PersistentIdEntry entry = new PersistentIdEntry();
-            entry.setIssuerEntityId(resultSet.getString(issuerColumn));
-            entry.setRecipientEntityId(resultSet.getString(recipientColumn));
+            final PairwiseId entry = new PairwiseId();
+            entry.setIssuerEntityID(resultSet.getString(issuerColumn));
+            entry.setRecipientEntityID(resultSet.getString(recipientColumn));
             entry.setPrincipalName(resultSet.getString(principalNameColumn));
-            entry.setPersistentId(resultSet.getString(persistentIdColumn));
-            entry.setSourceId(resultSet.getString(sourceIdColumn));
+            entry.setPairwiseId(resultSet.getString(persistentIdColumn));
+            entry.setSourceSystemId(resultSet.getString(sourceIdColumn));
             entry.setPeerProvidedId(resultSet.getString(peerProvidedIdColumn));
-            entry.setCreationTime(resultSet.getTimestamp(creationTimeColumn));
-            entry.setDeactivationTime(resultSet.getTimestamp(deactivationTimeColumn));
+            Timestamp ts = resultSet.getTimestamp(creationTimeColumn);
+            if (ts != null) {
+                entry.setCreationTime(ts.getTime());
+            }
+            ts = resultSet.getTimestamp(deactivationTimeColumn);
+            if (ts != null) {
+                entry.setDeactivationTime(ts.getTime());
+            }
             entries.add(entry);
     
-            log.trace("{} Entry {} added to results", getLogPrefix(), entry.toString());
+            log.trace("Entry {} added to results", entry.toString());
         }
     
         return entries;
     }
-
-    /**
-     * Return a string which is to be prepended to all log messages.
-     * 
-     * @return "Stored Id Store:"
-     */
-    @Nonnull @NotEmpty private String getLogPrefix() {
-        return "Stored Id Store:";
-    }
     
 }
\ No newline at end of file
diff --git a/idp-attribute-impl/src/main/java/net/shibboleth/idp/attribute/impl/package-info.java b/idp-attribute-impl/src/main/java/net/shibboleth/idp/attribute/impl/package-info.java
new file mode 100644
index 0000000..550f2e3
--- /dev/null
+++ b/idp-attribute-impl/src/main/java/net/shibboleth/idp/attribute/impl/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Implementations of general attribute functionality.
+ */
+
+package net.shibboleth.idp.attribute.impl;
\ No newline at end of file
diff --git a/idp-attribute-impl/src/main/resources/.gitkeep b/idp-attribute-impl/src/main/resources/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/idp-attribute-impl/src/test/java/.gitkeep b/idp-attribute-impl/src/test/java/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/impl/ComputedPairwiseIdStoreTest.java b/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/impl/ComputedPairwiseIdStoreTest.java
new file mode 100644
index 0000000..ab51fc8
--- /dev/null
+++ b/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/impl/ComputedPairwiseIdStoreTest.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.impl;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import net.shibboleth.idp.attribute.PairwiseId;
+import net.shibboleth.idp.attribute.impl.ComputedPairwiseIdStore.Encoding;
+import net.shibboleth.idp.testing.DatabaseTestingSupport;
+import net.shibboleth.utilities.java.support.codec.Base64Support;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+/**
+ * Test for {@link ComputedPairwiseIdStore}.
+ */
+public class ComputedPairwiseIdStoreTest {
+
+    /** Value calculated using V2 version. DO NOT CHANGE WITHOUT TESTING AGAINST 2.0 */
+    private static final String RESULT = "Vl6z6K70iLc4AuBoNeb59Dj1rGw=";
+
+    private static final String RESULT2 = "kLyH1uEvYigEvg1ZLh/QXeW1VAs=";
+
+    private static final String B32RESULT = "KZPLH2FO6SELOOAC4BUDLZXZ6Q4PLLDM";
+
+    private static final byte salt[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+
+    private static final String salt2 = "thisisaspecialsalt";
+        
+    public static final String COMMON_ATTRIBUTE_VALUE_STRING = "at1-Data";
+    
+    @Test(expectedExceptions = ComponentInitializationException.class)
+    public void testInvalidConfig() throws ComponentInitializationException {
+        final ComputedPairwiseIdStore store = new ComputedPairwiseIdStore();
+        store.initialize();
+    }
+
+    @Test
+    public void testSaltSetters() throws ComponentInitializationException {
+        final ComputedPairwiseIdStore store = new ComputedPairwiseIdStore();
+        store.setSalt(salt);
+        Assert.assertEquals(salt, store.getSalt());
+        
+        store.setEncodedSalt(Base64Support.encode(salt, false));
+        Assert.assertEquals(salt, store.getSalt());
+    }
+
+    @Test(expectedExceptions=IOException.class)
+    public void testRevoked() throws Exception {
+        final ComputedPairwiseIdStore store = new ComputedPairwiseIdStore();
+        store.setSalt(salt);
+        store.setExceptionMap(Collections.singletonMap("foo",
+                Collections.<String,String>singletonMap(DatabaseTestingSupport.SP_ENTITY_ID, null)));
+        store.initialize();
+        
+        PairwiseId pid = new PairwiseId();
+        pid.setRecipientEntityID(DatabaseTestingSupport.SP_ENTITY_ID);
+        pid.setPrincipalName("foo");
+        pid.setSourceSystemId(COMMON_ATTRIBUTE_VALUE_STRING);
+        
+        store.getBySourceValue(pid, true);
+    }
+    
+    @Test(expectedExceptions=IOException.class)
+    public void testRevokedWildcardRP() throws Exception {
+        final ComputedPairwiseIdStore store = new ComputedPairwiseIdStore();
+        store.setSalt(salt);
+        store.setExceptionMap(Collections.singletonMap("foo",
+                Collections.<String,String>singletonMap(ComputedPairwiseIdStore.WILDCARD_OVERRIDE, null)));
+        store.initialize();
+
+        PairwiseId pid = new PairwiseId();
+        pid.setRecipientEntityID(DatabaseTestingSupport.SP_ENTITY_ID);
+        pid.setPrincipalName("foo");
+        pid.setSourceSystemId(COMMON_ATTRIBUTE_VALUE_STRING);
+        
+        store.getBySourceValue(pid, true);
+    }
+    
+    @Test(expectedExceptions=IOException.class)
+    public void testRevokedWildcardUser() throws Exception {
+        final ComputedPairwiseIdStore store = new ComputedPairwiseIdStore();
+        store.setSalt(salt);
+        store.setExceptionMap(Collections.singletonMap(ComputedPairwiseIdStore.WILDCARD_OVERRIDE,
+                Collections.<String,String>singletonMap(DatabaseTestingSupport.SP_ENTITY_ID, null)));
+        store.initialize();
+
+        PairwiseId pid = new PairwiseId();
+        pid.setRecipientEntityID(DatabaseTestingSupport.SP_ENTITY_ID);
+        pid.setPrincipalName("foo");
+        pid.setSourceSystemId(COMMON_ATTRIBUTE_VALUE_STRING);
+        
+        store.getBySourceValue(pid, true);
+    }
+    
+    @Test
+    public void testComputedId() throws Exception {
+        final ComputedPairwiseIdStore store = new ComputedPairwiseIdStore();
+        store.setSalt(salt);
+        store.initialize();
+
+        PairwiseId pid = new PairwiseId();
+        pid.setRecipientEntityID(DatabaseTestingSupport.SP_ENTITY_ID);
+        pid.setPrincipalName("foo");
+        pid.setSourceSystemId(COMMON_ATTRIBUTE_VALUE_STRING);
+        
+        pid = store.getBySourceValue(pid, true);
+
+        Assert.assertNotNull(pid);
+        Assert.assertEquals(pid.getPairwiseId(), RESULT);
+    }
+    
+    @Test
+    public void testComputedIdOverride() throws Exception {
+        final ComputedPairwiseIdStore store = new ComputedPairwiseIdStore();
+        store.setSalt(salt);
+        store.setExceptionMap(Collections.singletonMap(ComputedPairwiseIdStore.WILDCARD_OVERRIDE,
+                Collections.<String,String>singletonMap(DatabaseTestingSupport.SP_ENTITY_ID, salt2)));
+        store.initialize();
+
+        PairwiseId pid = new PairwiseId();
+        pid.setRecipientEntityID(DatabaseTestingSupport.SP_ENTITY_ID);
+        pid.setPrincipalName("foo");
+        pid.setSourceSystemId(COMMON_ATTRIBUTE_VALUE_STRING);
+        
+        pid = store.getBySourceValue(pid, true);
+
+        Assert.assertNotNull(pid);
+        Assert.assertEquals(pid.getPairwiseId(), RESULT2);
+    }
+
+    @Test
+    public void testBase32ComputedId() throws Exception {
+        final ComputedPairwiseIdStore store = new ComputedPairwiseIdStore();
+        store.setSalt(salt);
+        store.setEncoding(Encoding.BASE32);
+        store.initialize();
+
+        PairwiseId pid = new PairwiseId();
+        pid.setRecipientEntityID(DatabaseTestingSupport.SP_ENTITY_ID);
+        pid.setPrincipalName("foo");
+        pid.setSourceSystemId(COMMON_ATTRIBUTE_VALUE_STRING);
+        
+        pid = store.getBySourceValue(pid, true);
+
+        Assert.assertNotNull(pid);
+        Assert.assertEquals(pid.getPairwiseId(), B32RESULT);
+    }
+   
+}
\ No newline at end of file
diff --git a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/nameid/impl/DatabaseBackedIDStoreTest.java b/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/impl/JDBCPairwiseIdStoreTest.java
similarity index 57%
rename from idp-saml-impl/src/test/java/net/shibboleth/idp/saml/nameid/impl/DatabaseBackedIDStoreTest.java
rename to idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/impl/JDBCPairwiseIdStoreTest.java
index 6c98629..a6b5ac8 100644
--- a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/nameid/impl/DatabaseBackedIDStoreTest.java
+++ b/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/impl/JDBCPairwiseIdStoreTest.java
@@ -15,63 +15,54 @@
  * limitations under the License.
  */
 
-package net.shibboleth.idp.saml.nameid.impl;
+package net.shibboleth.idp.attribute.impl;
 
 import java.io.IOException;
-import java.io.InputStreamReader;
 import java.sql.Connection;
 import java.sql.SQLException;
-import java.sql.Timestamp;
 import java.util.Objects;
 import java.util.UUID;
 
 import javax.annotation.Nonnull;
 import javax.sql.DataSource;
 
-import net.shibboleth.idp.saml.nameid.PersistentIdEntry;
+import net.shibboleth.idp.attribute.PairwiseId;
 import net.shibboleth.idp.testing.DatabaseTestingSupport;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.UninitializedComponentException;
 import net.shibboleth.utilities.java.support.component.UnmodifiableComponentException;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import com.google.common.io.CharStreams;
-
-/** Tests for {@link JDBCPersistentIdStoreEx}. */
-public class DatabaseBackedIDStoreTest {
-    
-    private final Logger log = LoggerFactory.getLogger(DatabaseBackedIDStoreTest.class);
+/** Tests for {@link JDBCPairwiseIdStore}. */
+public class JDBCPairwiseIdStoreTest {
 
     private DataSource testSource;
     
-    public static String convertStreamToString(java.io.InputStream is) throws IOException {
-        return CharStreams.toString(new InputStreamReader(is));
-    }
+    public static final String INIT_FILE = "/net/shibboleth/idp/attribute/impl/StoredIdStore.sql";
+    public static final String DELETE_FILE = "/net/shibboleth/idp/attribute/impl/DeleteStore.sql";
     
     @BeforeMethod
     public void setupSource() throws IOException, IOException  {
-        testSource = DatabaseTestingSupport.GetMockDataSource(PersistentSAML2NameIDGeneratorTest.INIT_FILE, "PersistentIdStore");
+        testSource = DatabaseTestingSupport.GetMockDataSource(INIT_FILE, "PersistentIdStore");
     }
     
     @AfterMethod public void teardown() {
-        DatabaseTestingSupport.InitializeDataSource(PersistentSAML2NameIDGeneratorTest.DELETE_FILE, testSource);
+        DatabaseTestingSupport.InitializeDataSource(DELETE_FILE, testSource);
     }
 
     
     @Test public void initializeAndGetters() throws ComponentInitializationException, IOException {
 
-        JDBCPersistentIdStoreEx store = new JDBCPersistentIdStoreEx();
+        final JDBCPairwiseIdStore store = new JDBCPairwiseIdStore();
         try {
             store.initialize();
             Assert.fail("Need to initialize the source");
         } catch (final ComponentInitializationException e) {
-            // OK 
+            // OK
         }
         store.setDataSource(testSource);
         
@@ -80,7 +71,7 @@ public class DatabaseBackedIDStoreTest {
         store.setQueryTimeout(1);
         
         try {
-            store.getBySourceValue("foo", "foo", "foo", "foo", true, null);
+            store.getBySourceValue(new PairwiseId(), true);
             Assert.fail("need to initialize first");
         } catch (final UninitializedComponentException e) {
             // OK
@@ -104,72 +95,69 @@ public class DatabaseBackedIDStoreTest {
         Assert.assertEquals(store.getQueryTimeout(), 1);
     }
     
-    private boolean comparePersistentIdEntrys(@Nonnull PersistentIdEntry one, @Nonnull PersistentIdEntry other)
+    private boolean comparePersistentIdEntrys(@Nonnull PairwiseId one, @Nonnull PairwiseId other)
     {
         //
         // Do not compare times
         //
-        boolean result = Objects.equals(one.getPersistentId(), other.getPersistentId()) &&
-                Objects.equals(one.getIssuerEntityId(), other.getIssuerEntityId()) &&
-                Objects.equals(one.getRecipientEntityId(), other.getRecipientEntityId()) &&
-                Objects.equals(one.getSourceId(), other.getSourceId()) &&
+        return Objects.equals(one.getPairwiseId(), other.getPairwiseId()) &&
+                Objects.equals(one.getIssuerEntityID(), other.getIssuerEntityID()) &&
+                Objects.equals(one.getRecipientEntityID(), other.getRecipientEntityID()) &&
+                Objects.equals(one.getSourceSystemId(), other.getSourceSystemId()) &&
                 Objects.equals(one.getPrincipalName(), other.getPrincipalName()) &&
                 Objects.equals(one.getPeerProvidedId(), other.getPeerProvidedId()) &&
                 Objects.equals(one.getDeactivationTime(), other.getDeactivationTime());
-        if (!result) {
-            log.warn("Not equals: {} and {}", one, other);
-        }
-        return result;
     }
    
     @Test public void storeEntry() throws ComponentInitializationException, IOException, SQLException {
-        JDBCPersistentIdStoreEx store = new JDBCPersistentIdStoreEx();
+        final JDBCPairwiseIdStore store = new JDBCPairwiseIdStore();
         store.setDataSource(testSource);
         store.setVerifyDatabase(true);
         store.initialize();
         
-        final PersistentIdEntry id = new PersistentIdEntry();
+        final PairwiseId id = new PairwiseId();
         String persistentId = UUID.randomUUID().toString();
         
-        id.setIssuerEntityId(DatabaseTestingSupport.IDP_ENTITY_ID);
-        id.setRecipientEntityId(DatabaseTestingSupport.SP_ENTITY_ID);
+        id.setIssuerEntityID(DatabaseTestingSupport.IDP_ENTITY_ID);
+        id.setRecipientEntityID(DatabaseTestingSupport.SP_ENTITY_ID);
         id.setPrincipalName(DatabaseTestingSupport.PRINCIPAL_ID);
-        id.setSourceId("localID");
+        id.setSourceSystemId("localID");
         id.setPeerProvidedId("PeerprovidedId");
-        id.setPersistentId(persistentId);
-        id.setCreationTime(new Timestamp(System.currentTimeMillis()));
+        id.setPairwiseId(persistentId);
+        id.setCreationTime(System.currentTimeMillis());
         
         try (final Connection conn = testSource.getConnection()) {
             store.store(id, conn);
-        } finally {
-            
         }
         
-        PersistentIdEntry gotback = store.getByIssuedValue(DatabaseTestingSupport.IDP_ENTITY_ID,
-                DatabaseTestingSupport.SP_ENTITY_ID, persistentId);
+        PairwiseId id2 = new PairwiseId();
+        id2.setIssuerEntityID(DatabaseTestingSupport.IDP_ENTITY_ID);
+        id2.setRecipientEntityID(DatabaseTestingSupport.SP_ENTITY_ID);
+        id2.setPairwiseId(persistentId);
+        id2 = store.getByIssuedValue(id2);
         
-        Assert.assertNull(gotback.getDeactivationTime());
-        Assert.assertTrue(comparePersistentIdEntrys(gotback, id));
+        Assert.assertNull(id2.getDeactivationTime());
+        Assert.assertTrue(comparePersistentIdEntrys(id2, id));
         
-        store.deactivate(DatabaseTestingSupport.IDP_ENTITY_ID, DatabaseTestingSupport.SP_ENTITY_ID, persistentId, null);
+        store.deactivate(id2);
         
-        Assert.assertNull(store.getByIssuedValue(DatabaseTestingSupport.IDP_ENTITY_ID,
-                DatabaseTestingSupport.SP_ENTITY_ID, persistentId));
+        Assert.assertNull(store.getByIssuedValue(id2));
      
         persistentId = UUID.randomUUID().toString();
-        id.setPersistentId(persistentId);
+        id.setPairwiseId(persistentId);
         id.setPeerProvidedId(null);
-        id.setRecipientEntityId(DatabaseTestingSupport.SP_ENTITY_ID + "2");
+        id.setRecipientEntityID(DatabaseTestingSupport.SP_ENTITY_ID + "2");
         try (final Connection conn = testSource.getConnection()) {
             store.store(id, conn);
-        } finally {
-            
         }
         
-        gotback = store.getByIssuedValue(DatabaseTestingSupport.IDP_ENTITY_ID,
-                DatabaseTestingSupport.SP_ENTITY_ID + "2", persistentId);
-        Assert.assertNull(gotback.getDeactivationTime());
-        Assert.assertTrue(comparePersistentIdEntrys(gotback, id));
+        PairwiseId id3 = new PairwiseId();
+        id3.setIssuerEntityID(DatabaseTestingSupport.IDP_ENTITY_ID);
+        id3.setRecipientEntityID(DatabaseTestingSupport.SP_ENTITY_ID + "2");
+        id3.setPairwiseId(persistentId);
+        id3 = store.getByIssuedValue(id3);
+        Assert.assertNull(id3.getDeactivationTime());
+        Assert.assertTrue(comparePersistentIdEntrys(id3, id));
     }
     
 }
\ No newline at end of file
diff --git a/idp-attribute-impl/src/test/resources/logback-test.xml b/idp-attribute-impl/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..f3280e5
--- /dev/null
+++ b/idp-attribute-impl/src/test/resources/logback-test.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<configuration>
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <pattern>%level [%logger:%line] - %msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <root>
+        <level value="warn" />
+        <appender-ref ref="STDOUT" />
+    </root>
+    
+</configuration>
\ No newline at end of file
diff --git a/idp-attribute-impl/src/test/resources/net/shibboleth/idp/attribute/impl/DeleteStore.sql b/idp-attribute-impl/src/test/resources/net/shibboleth/idp/attribute/impl/DeleteStore.sql
new file mode 100644
index 0000000..9ffe8bd
--- /dev/null
+++ b/idp-attribute-impl/src/test/resources/net/shibboleth/idp/attribute/impl/DeleteStore.sql
@@ -0,0 +1 @@
+DROP TABLE shibpid;
diff --git a/idp-attribute-impl/src/test/resources/net/shibboleth/idp/attribute/impl/StoredIdStore.sql b/idp-attribute-impl/src/test/resources/net/shibboleth/idp/attribute/impl/StoredIdStore.sql
new file mode 100644
index 0000000..294b108
--- /dev/null
+++ b/idp-attribute-impl/src/test/resources/net/shibboleth/idp/attribute/impl/StoredIdStore.sql
@@ -0,0 +1,11 @@
+CREATE TABLE shibpid (
+	localEntity VARCHAR(100) NOT NULL, 
+	peerEntity VARCHAR(100) NOT NULL,
+    persistentId VARCHAR(50) NOT NULL, 
+	principalName VARCHAR(50) NOT NULL, 
+	localId VARCHAR(50) NOT NULL, 
+	peerProvidedId VARCHAR(50), 
+	creationDate TIMESTAMP NOT NULL, 
+	deactivationDate TIMESTAMP,
+	PRIMARY KEY (localEntity, peerEntity, persistentId)
+	);
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/impl/PairwiseIdDataConnector.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/impl/PairwiseIdDataConnector.java
new file mode 100644
index 0000000..304173e
--- /dev/null
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/impl/PairwiseIdDataConnector.java
@@ -0,0 +1,310 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.resolver.dc.impl;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Strings;
+
+import net.shibboleth.idp.attribute.EmptyAttributeValue;
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.PairwiseId;
+import net.shibboleth.idp.attribute.PairwiseIdStore;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.resolver.AbstractDataConnector;
+import net.shibboleth.idp.attribute.resolver.PluginDependencySupport;
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.ResolverAttributeDefinitionDependency;
+import net.shibboleth.idp.attribute.resolver.ResolverDataConnectorDependency;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolverWorkContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/**
+ * A {@link net.shibboleth.idp.attribute.resolver.DataConnector} that generates pairwise IDs that depend on
+ * a seed {@link IdPAttribute}.
+ * 
+ * @since 4.0.0
+ */
+public class PairwiseIdDataConnector extends AbstractDataConnector {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(PairwiseIdDataConnector.class);
+
+    /** ID of the attribute generated by this data connector. */
+    @NonnullAfterInit private String generatedAttribute;
+
+    /** Information about the dependency. */
+    @NonnullAfterInit private String sourceInformation;
+    
+    /** Source of IDs. */
+    @NonnullAfterInit private PairwiseIdStore pidStore;
+
+    /**
+     * Get the attribute ID whose first value is being used when producing the pairwise ID.
+     *
+     * @return source attribute ID
+     */
+    @Nullable @NonnullAfterInit public String getSourceAttributeInformation() {
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+        return sourceInformation;
+    }
+
+    /**
+     * Get the ID of the attribute generated by this connector.
+     * 
+     * @return ID of the attribute generated by this connector
+     */
+    @NonnullAfterInit public String getGeneratedAttributeId() {
+        return generatedAttribute;
+    }
+
+    /**
+     * Set the ID of the attribute generated by this connector.
+     * 
+     * @param newAttributeId what to set.
+     */
+    public void setGeneratedAttributeId(@Nullable final String newAttributeId) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        generatedAttribute = newAttributeId;
+    }
+    
+    /**
+     * Get the underlying pairwise ID store.
+     * 
+     * @return ID store
+     */
+    @NonnullAfterInit public PairwiseIdStore getPairwiseIdStore() {
+        return pidStore;
+    }
+    
+    /**
+     * Set the underlying pairwise ID store.
+     * 
+     * @param store ID store
+     */
+    public void setPairwiseIdStore(@Nonnull final PairwiseIdStore store) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        pidStore = Constraint.isNotNull(store, "PairwiseIdStore cannot be null");
+    }
+    
+    /**
+     * Do the dance with dependencies.
+     *
+     * @throws ComponentInitializationException if the dependencies are not aligned correctly
+     */
+    private void doDependencyInformation() throws ComponentInitializationException {
+        final StringBuilder dependencyInformation = new StringBuilder();
+        boolean seenAttribute = false;
+
+        for (final ResolverAttributeDefinitionDependency attrDep : getAttributeDependencies()) {
+            if (seenAttribute) {
+                dependencyInformation.append(", ");
+            }
+            dependencyInformation.append(attrDep.getDependencyPluginId());
+            seenAttribute = true;
+        }
+
+        for (final ResolverDataConnectorDependency dataConnectorDependency : getDataConnectorDependencies()) {
+            if (seenAttribute) {
+                dependencyInformation.append(", ");
+            }
+            if (dataConnectorDependency.isAllAttributes()) {
+                dependencyInformation.append(dataConnectorDependency.getDependencyPluginId()).append("/*");
+            } else if (dataConnectorDependency.getAttributeNames().isEmpty()) {
+                throw new ComponentInitializationException(getLogPrefix() + " No source attribute present.");
+            } else if (dataConnectorDependency.getAttributeNames().size() == 1) {
+                dependencyInformation.append(dataConnectorDependency.getDependencyPluginId()).
+                                      append('/').
+                                      append(dataConnectorDependency.getAttributeNames().iterator().next());
+            } else {
+                dependencyInformation.append(dataConnectorDependency.getDependencyPluginId()).
+                                      append('/').
+                                      append(dataConnectorDependency.getAttributeNames().toString());
+            }
+            seenAttribute = true;
+        }
+        
+        if (!seenAttribute) {
+            throw new ComponentInitializationException(getLogPrefix() +
+                    " No source attribute present in the supplied Dependencies");
+        }
+        sourceInformation = dependencyInformation.toString();
+        log.debug("{} Source for definition: {}", getLogPrefix(), sourceInformation);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void doInitialize() throws ComponentInitializationException { 
+
+        if (pidStore == null) {
+            throw new ComponentInitializationException("PairwiseIdStore cannot be null");
+        }
+        
+        // Set up the dependencies first. Then the initialize in the parent
+        // will correctly rehash the dependencies.
+        doDependencyInformation();
+        super.doInitialize();
+
+        if (null == generatedAttribute) {
+            generatedAttribute = getId();
+            log.info("{} No generated attribute ID supplied, using ID of connector: {}", getLogPrefix(),
+                    generatedAttribute);
+        }
+    }
+
+    /**
+     * Helper function to locate the source Attribute in the dependencies.
+     * 
+     * @param workContext the context to look in
+     * @return the value, or null in any of the failure cases.
+     */
+    @Nullable protected String resolveSourceAttribute(@Nonnull final AttributeResolverWorkContext workContext) {
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+        final List<IdPAttributeValue<?>> attributeValues =
+                PluginDependencySupport.getMergedAttributeValues(workContext,
+                        getAttributeDependencies(),
+                        getDataConnectorDependencies(),
+                        getId());
+        if (attributeValues == null || attributeValues.isEmpty()) {
+            log.debug("{} Source attribute {} for connector {} provide no values", getLogPrefix(),
+                    getSourceAttributeInformation(), getId());
+            return null;
+        }
+
+        if (attributeValues.size() > 1) {
+            log.warn("{} Source attribute {} for connector {} has more than one value, only one value is used",
+                    getLogPrefix(), getSourceAttributeInformation(), getId());
+        }
+
+        final IdPAttributeValue attributeValue = attributeValues.iterator().next();
+
+        final String val;
+
+        if (attributeValue instanceof StringAttributeValue) {
+            if (StringSupport.trimOrNull((String) attributeValue.getValue()) == null) {
+                log.warn("{} Source attribute {} for connector {} was all-whitespace", getLogPrefix(),
+                        getSourceAttributeInformation(), getId());
+                return null;
+            }
+            val = (String) attributeValue.getValue();
+        } else if (attributeValue instanceof EmptyAttributeValue) {
+            final EmptyAttributeValue emptyVal = (EmptyAttributeValue) attributeValue;
+            log.warn("{} Source attribute {} value for connector {} was an empty value of type {}", getLogPrefix(),
+                    getSourceAttributeInformation(), getId(), emptyVal.getDisplayValue());
+            return null;
+        } else {
+            log.warn("{} Source attribute {} for connector {} was of an unsupported type: {}", getLogPrefix(),
+                    getSourceAttributeInformation(), getId(), attributeValue.getClass().getName());
+            return null;
+        }
+
+        if (val == null) {
+            log.warn("{} Attribute value {} for connector {} resolved as empty or null", getLogPrefix(),
+                    getSourceAttributeInformation(), getId());
+        }
+        return val;
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nullable protected Map<String, IdPAttribute> doDataConnectorResolve(
+            @Nonnull final AttributeResolutionContext resolutionContext,
+            @Nonnull final AttributeResolverWorkContext workContext) throws ResolutionException {
+
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+
+        final String principalName = resolutionContext.getPrincipal();
+        if (Strings.isNullOrEmpty(principalName)) {
+            log.warn("{} No principal name available, skipping ID creation", getLogPrefix());
+            return null;
+        }
+
+        final String attributeIssuerID = resolutionContext.getAttributeIssuerID();
+        if (Strings.isNullOrEmpty(attributeIssuerID)) {
+            log.warn("{} Could not get attribute issuer ID, skipping ID creation", getLogPrefix());
+            return null;
+        }
+
+        String attributeRecipientID = resolutionContext.getAttributeRecipientGroupID();
+        if (Strings.isNullOrEmpty(attributeRecipientID)) {
+            attributeRecipientID = resolutionContext.getAttributeRecipientID();
+        }
+        if (Strings.isNullOrEmpty(attributeRecipientID)) {
+            log.warn("{} Could not get attribute recipient or group ID, skipping ID creation", getLogPrefix());
+            return null;
+        }
+
+        final String sourceId = resolveSourceAttribute(workContext);
+        if (Strings.isNullOrEmpty(sourceId)) {
+            return null;
+        }
+        
+        try {
+            PairwiseId pid = new PairwiseId();
+            pid.setIssuerEntityID(attributeIssuerID);
+            pid.setRecipientEntityID(attributeRecipientID);
+            pid.setPrincipalName(principalName);
+            pid.setSourceSystemId(sourceId);
+            
+            pid = pidStore.getBySourceValue(pid, true);
+            if (pid != null && pid.getPairwiseId() != null) {
+                return encodeAsAttribute(pid.getPairwiseId());
+            } else {
+                log.warn("{} Pairwise ID store did not return a value", getLogPrefix());
+                return null;
+            }
+        } catch (final IOException e) {
+            throw new ResolutionException(e);
+        }
+    }
+    
+    /**
+     * Encode the provided string.
+     * 
+     * @param value the value to encode or null if that failed
+     * @return null or the attribute.
+     */
+    @Nullable protected Map<String, IdPAttribute> encodeAsAttribute(@Nullable final String value) {
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+        if (null == value) {
+            // The message will have been logged above
+            return null;
+        }
+        final IdPAttribute attribute = new IdPAttribute(getGeneratedAttributeId());
+        attribute.setValues(Collections.singletonList(StringAttributeValue.valueOf(value)));
+        return Collections.singletonMap(getGeneratedAttributeId(), attribute);
+    }
+
+}
\ No newline at end of file
diff --git a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/impl/ComputedIDDataConnectorTest.java b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/impl/ComputedIDDataConnectorTest.java
index 2d03a0f..461dfcb 100644
--- a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/impl/ComputedIDDataConnectorTest.java
+++ b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/impl/ComputedIDDataConnectorTest.java
@@ -33,6 +33,8 @@ import net.shibboleth.idp.attribute.EmptyAttributeValue.EmptyType;
 import net.shibboleth.idp.attribute.IdPAttribute;
 import net.shibboleth.idp.attribute.IdPAttributeValue;
 import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.impl.ComputedPairwiseIdStore;
+import net.shibboleth.idp.attribute.impl.JDBCPairwiseIdStore;
 import net.shibboleth.idp.attribute.resolver.AttributeDefinition;
 import net.shibboleth.idp.attribute.resolver.AttributeResolver;
 import net.shibboleth.idp.attribute.resolver.DataConnector;
@@ -43,15 +45,13 @@ import net.shibboleth.idp.attribute.resolver.ad.impl.SimpleAttributeDefinition;
 import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
 import net.shibboleth.idp.attribute.resolver.impl.AttributeResolverImpl;
 import net.shibboleth.idp.attribute.resolver.impl.AttributeResolverImplTest;
-import net.shibboleth.idp.saml.attribute.resolver.impl.AbstractPersistentIdDataConnector;
-import net.shibboleth.idp.saml.attribute.resolver.impl.ComputedIDDataConnector;
 import net.shibboleth.idp.saml.impl.TestSources;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
-import net.shibboleth.utilities.java.support.component.UnmodifiableComponentException;
+import net.shibboleth.utilities.java.support.component.InitializableComponent;
 
 /**
- * Test for {@link AbstractPersistentIdDataConnector}
+ * Test for {@link PairwiseIdDataConnector} with computed store.
  */
 public class ComputedIDDataConnectorTest extends OpenSAMLInitBaseTestCase {
 
@@ -71,7 +71,7 @@ public class ComputedIDDataConnectorTest extends OpenSAMLInitBaseTestCase {
 
     protected static final byte smallSalt[] = {0, 1, 2};
 
-    private static void testInit(final AbstractPersistentIdDataConnector connector, final String failMessage) {
+    private static void testInit(final PairwiseIdDataConnector connector, final String failMessage) {
         try {
             connector.initialize();
             Assert.fail(failMessage);
@@ -81,13 +81,17 @@ public class ComputedIDDataConnectorTest extends OpenSAMLInitBaseTestCase {
     }
 
     @Test public void dataConnector() throws ComponentInitializationException, ResolutionException {
-        final ComputedIDDataConnector connector = new ComputedIDDataConnector();
-
+        
+        final ComputedPairwiseIdStore store = new ComputedPairwiseIdStore();
+        store.setSalt(salt);
+        store.initialize();
+        
+        final PairwiseIdDataConnector connector = new PairwiseIdDataConnector();
         connector.setId(TEST_CONNECTOR_NAME);
         connector.setAttributeDependencies(Collections.singleton(TestSources.makeAttributeDefinitionDependency(
                 TestSources.DEPENDS_ON_ATTRIBUTE_NAME_ATTR)));
         testInit(connector, "No salt");
-        connector.setSalt(salt);
+        connector.setPairwiseIdStore(store);
         connector.setGeneratedAttributeId(TEST_ATTRIBUTE_NAME);
         connector.initialize();
 
@@ -119,43 +123,35 @@ public class ComputedIDDataConnectorTest extends OpenSAMLInitBaseTestCase {
         Assert.assertEquals(((StringAttributeValue) resultValues.iterator().next()).getValue(), RESULT);
     }
 
-    @Test public void getters() throws ComponentInitializationException {
-        final ComputedIDDataConnector connector = new ComputedIDDataConnector();
-        connector.setId(TEST_CONNECTOR_NAME);
-        connector.setAttributeDependencies(Collections.singleton(TestSources.makeAttributeDefinitionDependency(
-                TestSources.DEPENDS_ON_ATTRIBUTE_NAME_ATTR)));
-        connector.setSalt(smallSalt);
-        connector.setGeneratedAttributeId(TEST_ATTRIBUTE_NAME);
-        Assert.assertEquals(connector.getSalt(), smallSalt);
-        testInit(connector, "Small salt");
-        connector.setSalt(salt);
-        connector.initialize();
-
-        try {
-            connector.setSalt(smallSalt);
-            Assert.fail("setting after init");
-        } catch (final UnmodifiableComponentException e) {
-            // OK'
-        }
-
-        Assert.assertEquals(connector.getSalt(), salt);
-    }
-
     private AttributeResolver constructResolver(final int values) throws ComponentInitializationException {
-        final ComputedIDDataConnector connector = new ComputedIDDataConnector();
-
+        final PairwiseIdDataConnector connector = new PairwiseIdDataConnector();        
         return constructResolver(connector, values, false);
     }
 
-    protected static AttributeResolver constructResolver(final ComputedIDDataConnector connector, final int values, final boolean noSalt)
+    protected static AttributeResolver constructResolver(final PairwiseIdDataConnector connector, final int values, final boolean noSalt)
             throws ComponentInitializationException {
+        
+        if (!noSalt) {
+            final ComputedPairwiseIdStore store = new ComputedPairwiseIdStore();
+            store.setSalt(salt);
+            store.initialize();
+            if (connector.getPairwiseIdStore() != null) {
+                ((JDBCPairwiseIdStore) connector.getPairwiseIdStore()).setInitialValueStore(store);
+            } else {
+                connector.setPairwiseIdStore(store);
+            }
+        }
+        
+        if (connector.getPairwiseIdStore() != null) {
+            ((InitializableComponent) connector.getPairwiseIdStore()).initialize();
+        }
+        
         connector.setId(TEST_CONNECTOR_NAME);
         connector.setAttributeDependencies(Collections.singleton(TestSources.makeAttributeDefinitionDependency(
                 TestSources.DEPENDS_ON_ATTRIBUTE_NAME_ATTR)));
-        if (!noSalt) {
-            connector.setSalt(salt);
-        }
 
+        connector.initialize();
+        
         final SimpleAttributeDefinition simple = new SimpleAttributeDefinition();
         simple.setId(OUTPUT_ATTRIBUTE_NAME);
         simple.setDataConnectorDependencies(Collections.singleton(TestSources.makeDataConnectorDependency(TEST_CONNECTOR_NAME,
@@ -172,14 +168,25 @@ public class ComputedIDDataConnectorTest extends OpenSAMLInitBaseTestCase {
 
     private AttributeResolver constructResolverWithNonString(final String dependantOn)
             throws ComponentInitializationException {
-        return constructResolverWithNonString(new ComputedIDDataConnector(), dependantOn);
+        return constructResolverWithNonString(new PairwiseIdDataConnector(), dependantOn);
     }
 
-    protected static AttributeResolver constructResolverWithNonString(final ComputedIDDataConnector connector,
+    protected static AttributeResolver constructResolverWithNonString(final PairwiseIdDataConnector connector,
             final String dependantOn) throws ComponentInitializationException {
+        
+        final ComputedPairwiseIdStore store = new ComputedPairwiseIdStore();
+        store.setSalt(salt);
+        
+        if (connector.getPairwiseIdStore() != null) {
+            ((JDBCPairwiseIdStore) connector.getPairwiseIdStore()).setInitialValueStore(store);
+        } else {
+            connector.setPairwiseIdStore(store);
+        }
+        ((InitializableComponent) connector.getPairwiseIdStore()).initialize();
+        
         connector.setId(TEST_CONNECTOR_NAME);
         connector.setAttributeDependencies(Collections.singleton(TestSources.makeAttributeDefinitionDependency(dependantOn)));
-        connector.setSalt(salt);
+        connector.initialize();
 
         final SimpleAttributeDefinition simple = new SimpleAttributeDefinition();
         simple.setId(OUTPUT_ATTRIBUTE_NAME);
@@ -195,12 +202,12 @@ public class ComputedIDDataConnectorTest extends OpenSAMLInitBaseTestCase {
         return AttributeResolverImplTest.newAttributeResolverImpl("atresolver", set, Collections.singleton((DataConnector) connector));
     }
 
-    protected static AbstractPersistentIdDataConnector connectorFromResolver(final AttributeResolver resolver) {
-        return (AbstractPersistentIdDataConnector) resolver.getDataConnectors().get(TEST_CONNECTOR_NAME);
+    protected static PairwiseIdDataConnector connectorFromResolver(final AttributeResolver resolver) {
+        return (PairwiseIdDataConnector) resolver.getDataConnectors().get(TEST_CONNECTOR_NAME);
     }
     
-    @Test(expectedExceptions={ComponentInitializationException.class,}) public void noSalt() throws ComponentInitializationException {
-        connectorFromResolver(constructResolver(new ComputedIDDataConnector(), 1, true)).initialize();
+    @Test(expectedExceptions={ComponentInitializationException.class,}) public void noStore() throws ComponentInitializationException {
+        connectorFromResolver(constructResolver(new PairwiseIdDataConnector(), 1, true)).initialize();
     }
 
     @Test public void altDataConnector() throws ComponentInitializationException, ResolutionException {
@@ -291,12 +298,16 @@ public class ComputedIDDataConnectorTest extends OpenSAMLInitBaseTestCase {
 
     @Test public void case425() throws ComponentInitializationException, ResolutionException {
 
-        final ComputedIDDataConnector connector = new ComputedIDDataConnector();
+        final ComputedPairwiseIdStore store = new ComputedPairwiseIdStore();
+        store.setSalt(salt);
+        store.initialize();
+
+        final PairwiseIdDataConnector connector = new PairwiseIdDataConnector();
         connector.setId(TEST_CONNECTOR_NAME);
         connector.setDataConnectorDependencies(Collections.singleton(TestSources.makeDataConnectorDependency(
                 TestSources.STATIC_CONNECTOR_NAME, TestSources.DEPENDS_ON_ATTRIBUTE_NAME_CONNECTOR)));
-        connector.setSalt(salt);
         connector.setGeneratedAttributeId("wibble");
+        connector.setPairwiseIdStore(store);
         connector.initialize();
 
         final Set<DataConnector> set = new HashSet<>(2);
@@ -342,12 +353,15 @@ public class ComputedIDDataConnectorTest extends OpenSAMLInitBaseTestCase {
 
         final ResolverDataConnectorDependency depend = TestSources.makeDataConnectorDependency("connector1", ResolverTestSupport.EPA_ATTRIB_ID);
 
+        final ComputedPairwiseIdStore store = new ComputedPairwiseIdStore();
+        store.setSalt(salt);
+        store.initialize();
 
-        final ComputedIDDataConnector connector = new ComputedIDDataConnector();
+        final PairwiseIdDataConnector connector = new PairwiseIdDataConnector();
         connector.setId(TEST_CONNECTOR_NAME);
         connector.setDataConnectorDependencies(Collections.singleton(depend));
-        connector.setSalt(salt);
         connector.setGeneratedAttributeId("wibble");
+        connector.setPairwiseIdStore(store);
         connector.initialize();
 
         
@@ -371,12 +385,15 @@ public class ComputedIDDataConnectorTest extends OpenSAMLInitBaseTestCase {
 
         final ResolverDataConnectorDependency depend = TestSources.makeDataConnectorDependency("connector1", ResolverTestSupport.EPA_ATTRIB_ID);
 
+        final ComputedPairwiseIdStore store = new ComputedPairwiseIdStore();
+        store.setSalt(salt);
+        store.initialize();
 
-        final ComputedIDDataConnector connector = new ComputedIDDataConnector();
+        final PairwiseIdDataConnector connector = new PairwiseIdDataConnector();
         connector.setId(TEST_CONNECTOR_NAME);
         connector.setDataConnectorDependencies(Collections.singleton(depend));
-        connector.setSalt(salt);
         connector.setGeneratedAttributeId("wibble");
+        connector.setPairwiseIdStore(store);
         connector.initialize();
 
         final Map<String, IdPAttribute> result = connector.resolve(resolutionContext);
diff --git a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/impl/StoredIDDataConnectorTest.java b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/impl/StoredIDDataConnectorTest.java
index 7ffb811..2edaab8 100644
--- a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/impl/StoredIDDataConnectorTest.java
+++ b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/impl/StoredIDDataConnectorTest.java
@@ -18,26 +18,23 @@
 package net.shibboleth.idp.attribute.resolver.dc.impl;
 
 import java.io.IOException;
-import java.io.InputStreamReader;
 import java.sql.SQLException;
-import java.util.Collections;
 import java.util.List;
 
 import javax.sql.DataSource;
 
+import net.shibboleth.idp.attribute.DurablePairwiseIdStore;
 import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.PairwiseId;
 import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.impl.JDBCPairwiseIdStore;
 import net.shibboleth.idp.attribute.resolver.AttributeResolver;
 import net.shibboleth.idp.attribute.resolver.ResolutionException;
-import net.shibboleth.idp.attribute.resolver.ResolverAttributeDefinitionDependency;
 import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
-import net.shibboleth.idp.saml.attribute.resolver.impl.StoredIDDataConnector;
 import net.shibboleth.idp.saml.impl.TestSources;
 import net.shibboleth.idp.testing.DatabaseTestingSupport;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
-import net.shibboleth.utilities.java.support.component.UninitializedComponentException;
-import net.shibboleth.utilities.java.support.component.UnmodifiableComponentException;
 
 import org.opensaml.core.OpenSAMLInitBaseTestCase;
 import org.testng.Assert;
@@ -45,29 +42,17 @@ import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Test;
 
-import com.google.common.io.CharStreams;
-
 /**
- * Tests for {@link StoredIDDataConnector} Placed here for convenience.
+ * Test for {@link PairwiseIdDataConnector} with JDBC store.
  */
 public class StoredIDDataConnectorTest extends OpenSAMLInitBaseTestCase {
 
-    /** The attribute name. */
-    private static final String TEST_ATTRIBUTE_NAME = "storedAttribute";
-
-    /** The connector name. */
-    private static final String TEST_CONNECTOR_NAME = "storedAttributeConnector";
-
     private static final String INIT_FILE = "/net/shibboleth/idp/attribute/resolver/impl/dc/StoredIdStore.sql";
 
     private static final String DELETE_FILE = "/net/shibboleth/idp/attribute/resolver/impl/dc/DeleteStore.sql";
 
     private DataSource testSource;
 
-    public static String convertStreamToString(final java.io.InputStream is) throws IOException {
-        return CharStreams.toString(new InputStreamReader(is));
-    }
-
     @BeforeTest public void setupSource() throws SQLException, IOException {
 
         testSource = DatabaseTestingSupport.GetMockDataSource(INIT_FILE, "StoredIDDataConnectorStore");
@@ -77,80 +62,17 @@ public class StoredIDDataConnectorTest extends OpenSAMLInitBaseTestCase {
         DatabaseTestingSupport.InitializeDataSource(DELETE_FILE, testSource);
     }
 
-    private void tryInitialize(final StoredIDDataConnector connector, final String failMessage) {
-        try {
-            connector.initialize();
-            Assert.fail(failMessage);
-        } catch (final ComponentInitializationException e) {
-            // OK
-        }
-
-    }
-
-    @Test public void initializeAndGetters() throws ComponentInitializationException, SQLException, ResolutionException {
-
-        StoredIDDataConnector connector = new StoredIDDataConnector();
-        connector.setId(TEST_CONNECTOR_NAME);
-        connector.setGeneratedAttributeId(TEST_ATTRIBUTE_NAME);
-
-        tryInitialize(connector, "No DataSource");
-        connector = new StoredIDDataConnector();
-        connector.setId(TEST_CONNECTOR_NAME);
-        connector.setGeneratedAttributeId(TEST_ATTRIBUTE_NAME);
-        connector.setDataSource(testSource);
-
-        connector = new StoredIDDataConnector();
-        connector.setId(TEST_CONNECTOR_NAME);
-        connector.setGeneratedAttributeId(TEST_ATTRIBUTE_NAME);
-        connector.setDataSource(testSource);
-        connector.setSalt(ComputedIDDataConnectorTest.smallSalt);
-        tryInitialize(connector, "salt too small");
-
-        connector = new StoredIDDataConnector();
-        connector.setId(TEST_CONNECTOR_NAME);
-        connector.setGeneratedAttributeId(TEST_ATTRIBUTE_NAME);
-        connector.setDataSource(testSource);
-        connector.setSalt(ComputedIDDataConnectorTest.smallSalt);
-        connector.setSalt(ComputedIDDataConnectorTest.salt);
-
-        Assert.assertEquals(connector.getDataSource(), testSource);
-        Assert.assertEquals(connector.getQueryTimeout(), 5000);
-        connector.setQueryTimeout(1);
-
-        try {
-            connector.resolve(null);
-            Assert.fail("need to initialize first");
-        } catch (final UninitializedComponentException e) {
-            // OK
-        }
-        connector.setAttributeDependencies(Collections.singleton(new ResolverAttributeDefinitionDependency("id")));
-
-        connector.initialize();
-        try {
-            connector.setDataSource(null);
-            Assert.fail("work after initialize");
-        } catch (final UnmodifiableComponentException e) {
-            // OK
-        }
-        connector.initialize();
-        try {
-            connector.setQueryTimeout(0);
-            Assert.fail("work after initialize");
-        } catch (final UnmodifiableComponentException e) {
-            // OK
-        }
-        Assert.assertEquals(connector.getDataSource(), testSource);
-        Assert.assertEquals(connector.getStoredIDStore().getDataSource(), testSource);
-        Assert.assertEquals(connector.getQueryTimeout(), 1);
-    }
-    
     private AttributeResolver constructResolver(final int values) throws ComponentInitializationException {
         return constructResolver(values, false);
     }
     
     private AttributeResolver constructResolver(final int values, final boolean noSalt) throws ComponentInitializationException {
-        final StoredIDDataConnector connector = new StoredIDDataConnector();
-        connector.setDataSource(testSource);
+        
+        final JDBCPairwiseIdStore store = new JDBCPairwiseIdStore();
+        store.setDataSource(testSource);
+        
+        final PairwiseIdDataConnector connector = new PairwiseIdDataConnector();
+        connector.setPairwiseIdStore(store);
 
         return ComputedIDDataConnectorTest.constructResolver(connector, values, noSalt);
     }
@@ -167,6 +89,7 @@ public class StoredIDDataConnectorTest extends OpenSAMLInitBaseTestCase {
         final AttributeResolver resolver = constructResolver(1);
 
         ComponentSupport.initialize(resolver);
+        
         ComputedIDDataConnectorTest.connectorFromResolver(resolver).initialize();
 
         final AttributeResolutionContext context =
@@ -257,12 +180,16 @@ public class StoredIDDataConnectorTest extends OpenSAMLInitBaseTestCase {
         resolver = constructResolver(1);
 
         
-        final StoredIDDataConnector connector =
-                (StoredIDDataConnector) ComputedIDDataConnectorTest.connectorFromResolver(resolver);
+        final PairwiseIdDataConnector connector =
+                (PairwiseIdDataConnector) ComputedIDDataConnectorTest.connectorFromResolver(resolver);
         ComponentSupport.initialize(resolver);
         connector.initialize();
-        connector.getStoredIDStore().deactivate(TestSources.IDP_ENTITY_ID, TestSources.SP_ENTITY_ID,
-                ComputedIDDataConnectorTest.RESULT, null);
+        
+        final PairwiseId pid = new PairwiseId();
+        pid.setIssuerEntityID(TestSources.IDP_ENTITY_ID);
+        pid.setRecipientEntityID(TestSources.SP_ENTITY_ID);
+        pid.setPairwiseId(ComputedIDDataConnectorTest.RESULT);
+        ((DurablePairwiseIdStore) connector.getPairwiseIdStore()).deactivate(pid);
 
         context =
                 TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
@@ -281,16 +208,23 @@ public class StoredIDDataConnectorTest extends OpenSAMLInitBaseTestCase {
 
     @Test(dependsOnMethods = {"retrieveEntry"}) void badEntry() throws ComponentInitializationException,
             IOException, ResolutionException {
-        final StoredIDDataConnector connector = new StoredIDDataConnector();
-        connector.setDataSource(testSource);
+        
+        final JDBCPairwiseIdStore store = new JDBCPairwiseIdStore();
+        store.setDataSource(testSource);
+        
+        final PairwiseIdDataConnector connector = new PairwiseIdDataConnector();
+        connector.setPairwiseIdStore(store);
 
         final AttributeResolver resolver = ComputedIDDataConnectorTest.constructResolverWithNonString(connector, "nonString");
 
         ComponentSupport.initialize(resolver);
         ComputedIDDataConnectorTest.connectorFromResolver(resolver).initialize();
 
-        connector.getStoredIDStore().deactivate(TestSources.IDP_ENTITY_ID, TestSources.SP_ENTITY_ID,
-                ComputedIDDataConnectorTest.RESULT, null);
+        final PairwiseId pid = new PairwiseId();
+        pid.setIssuerEntityID(TestSources.IDP_ENTITY_ID);
+        pid.setRecipientEntityID(TestSources.SP_ENTITY_ID);
+        pid.setPairwiseId(ComputedIDDataConnectorTest.RESULT);
+        ((DurablePairwiseIdStore) connector.getPairwiseIdStore()).deactivate(pid);
 
         final AttributeResolutionContext context =
                 TestSources.createResolutionContext(" ", TestSources.IDP_ENTITY_ID, TestSources.SP_ENTITY_ID);
diff --git a/idp-attribute-resolver-spring/pom.xml b/idp-attribute-resolver-spring/pom.xml
index d00feac..72b4a4a 100644
--- a/idp-attribute-resolver-spring/pom.xml
+++ b/idp-attribute-resolver-spring/pom.xml
@@ -34,6 +34,11 @@
         </dependency>
         <dependency>
             <groupId>${project.groupId}</groupId>
+            <artifactId>idp-attribute-impl</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
             <artifactId>idp-attribute-resolver-api</artifactId>
             <version>${project.version}</version>
         </dependency>
@@ -154,6 +159,11 @@
         </dependency>
 
         <dependency>
+            <groupId>org.ldaptive</groupId>
+            <artifactId>ldaptive-unboundid</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>com.unboundid</groupId>
             <artifactId>unboundid-ldapsdk</artifactId>
             <scope>test</scope>
diff --git a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/impl/ComputedIDDataConnectorParser.java b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/impl/ComputedIDDataConnectorParser.java
index 8926c9b..c7a45ac 100644
--- a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/impl/ComputedIDDataConnectorParser.java
+++ b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/impl/ComputedIDDataConnectorParser.java
@@ -20,9 +20,12 @@ package net.shibboleth.idp.attribute.resolver.spring.dc.impl;
 import javax.annotation.Nonnull;
 import javax.xml.namespace.QName;
 
+import net.shibboleth.idp.attribute.impl.ComputedPairwiseIdStore;
 import net.shibboleth.idp.attribute.resolver.spring.impl.AttributeResolverNamespaceHandler;
-import net.shibboleth.idp.saml.attribute.resolver.impl.ComputedIDDataConnector;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
 import org.springframework.beans.factory.xml.ParserContext;
 import org.w3c.dom.Element;
@@ -30,20 +33,63 @@ import org.w3c.dom.Element;
 /**
  * Spring bean definition parser for configuring {@link ComputedIDDataConnector}.
  */
-public class ComputedIDDataConnectorParser extends BaseComputedIDDataConnectorParser {
+public class ComputedIDDataConnectorParser extends PairwiseIdDataConnectorParser {
 
     /** Schema type - resolver. */
     @Nonnull public static final QName TYPE_NAME_RESOLVER = new QName(AttributeResolverNamespaceHandler.NAMESPACE, 
             "ComputedId");
 
-    /** {@inheritDoc} */
-    @Override protected Class<ComputedIDDataConnector> getBeanClass(final Element element) {
-        return ComputedIDDataConnector.class;
-    }
-
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ComputedIDDataConnectorParser.class);
+    
     /** {@inheritDoc} */
     @Override protected void doParse(@Nonnull final Element config, @Nonnull final ParserContext parserContext,
             @Nonnull final BeanDefinitionBuilder builder) {
         super.doParse(config, parserContext, builder, "computedId");
+
+        builder.addPropertyValue("pairwiseIdStore", doComputedPairwiseIdStore(config, parserContext));
+    }
+
+    /**
+     * Parse the config and define a bean for a {@link ComputedPairwiseIdStore}.
+     * 
+     * @param config the XML element being parsed
+     * @param parserContext the object encapsulating the current state of the parsing process
+     * @return bean definition for the store object to inject
+     */
+    @Nonnull protected BeanDefinition doComputedPairwiseIdStore(@Nonnull final Element config,
+            @Nonnull final ParserContext parserContext) {
+        
+        final BeanDefinitionBuilder builder =
+                BeanDefinitionBuilder.genericBeanDefinition(ComputedPairwiseIdStore.class);
+        builder.setInitMethodName("initialize");
+        builder.setDestroyMethodName("destroy");
+
+        if (config.hasAttributeNS(null, "algorithm")) {
+            builder.addPropertyValue("algorithm", config.getAttributeNS(null, "algorithm"));
+        }
+
+        if (config.hasAttributeNS(null, "encoding")) {
+            builder.addPropertyValue("encoding", config.getAttributeNS(null, "encoding"));
+        }
+
+        final String salt;
+        if (config.hasAttributeNS(null, "salt")) {
+            salt = config.getAttributeNS(null, "salt");
+        } else {
+            salt = null;
+        }
+        
+        if (null == salt) {
+            log.debug("{} No salt provided", getLogPrefix());
+        } else {
+            log.debug("{} See TRACE log for the salt value", getLogPrefix());
+            log.trace("{} Salt: '{}'", getLogPrefix(), salt);
+        }
+
+        builder.addPropertyValue("salt", salt);
+
+        return builder.getBeanDefinition();
     }
-    }
\ No newline at end of file
+
+}
\ No newline at end of file
diff --git a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/impl/BaseComputedIDDataConnectorParser.java b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/impl/PairwiseIdDataConnectorParser.java
similarity index 67%
rename from idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/impl/BaseComputedIDDataConnectorParser.java
rename to idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/impl/PairwiseIdDataConnectorParser.java
index 62923e6..d2bb323 100644
--- a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/impl/BaseComputedIDDataConnectorParser.java
+++ b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/impl/PairwiseIdDataConnectorParser.java
@@ -29,33 +29,46 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
 import org.springframework.beans.factory.xml.ParserContext;
 import org.w3c.dom.Element;
 
+import net.shibboleth.idp.attribute.resolver.dc.impl.PairwiseIdDataConnector;
 import net.shibboleth.idp.attribute.resolver.spring.BaseResolverPluginParser;
 import net.shibboleth.idp.attribute.resolver.spring.dc.AbstractDataConnectorParser;
+import net.shibboleth.idp.attribute.resolver.spring.impl.AttributeResolverNamespaceHandler;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.primitive.StringSupport;
-import net.shibboleth.utilities.java.support.xml.AttributeSupport;
 import net.shibboleth.utilities.java.support.xml.ElementSupport;
 
 /**
  * Spring bean definition parser for configuring
- * {@link net.shibboleth.idp.saml.attribute.resolver.impl.ComputedIDDataConnector} and
- * {@link net.shibboleth.idp.saml.attribute.resolver.impl.StoredIDDataConnector}.
+ * {@link net.shibboleth.idp.attribute.resolver.dc.impl.PairwiseIdDataConnector} variants.
  */
-public abstract class BaseComputedIDDataConnectorParser extends BaseResolverPluginParser {
+public class PairwiseIdDataConnectorParser extends BaseResolverPluginParser {
 
+    /** Schema type - resolver. */
+    @Nonnull public static final QName TYPE_NAME_RESOLVER = new QName(AttributeResolverNamespaceHandler.NAMESPACE, 
+            "PairwiseId");
+    
     /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(BaseComputedIDDataConnectorParser.class);
+    @Nonnull private final Logger log = LoggerFactory.getLogger(PairwiseIdDataConnectorParser.class);
+
+    /** {@inheritDoc} */
+    @Override protected Class<PairwiseIdDataConnector> getBeanClass(final Element element) {
+        return PairwiseIdDataConnector.class;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void doParse(@Nonnull final Element config, @Nonnull final ParserContext parserContext,
+            @Nonnull final BeanDefinitionBuilder builder) {
+        doParse(config, parserContext, builder, "pairwiseId");
+    }
 
     /**
-     * Parse the common definitions for {@link net.shibboleth.idp.saml.attribute.resolver.impl.ComputedIDDataConnector}
-     * and {@link net.shibboleth.idp.saml.attribute.resolver.impl.StoredIDDataConnector}.
+     * Parse any common material for {@link net.shibboleth.idp.attribute.resolver.dc.impl.PairwiseIdDataConnector}.
      * 
      * @param config the DOM element under consideration.
      * @param parserContext Spring's context.
      * @param builder Spring's bean builder.
      * @param generatedIdDefaultName the name to give the generated Attribute if none was provided.
      */
-    // Checkstyle: CyclomaticComplexity|MethodLength OFF
     protected void doParse(@Nonnull final Element config, @Nonnull final ParserContext parserContext,
             @Nonnull final BeanDefinitionBuilder builder, @Nullable final String generatedIdDefaultName) {
         super.doParse(config, parserContext, builder);
@@ -66,50 +79,28 @@ public abstract class BaseComputedIDDataConnectorParser extends BaseResolverPlug
         } else {
             generatedAttribute = generatedIdDefaultName;
         }
+        builder.addPropertyValue("generatedAttributeId", generatedAttribute);
+        
+        // If this isn't one of the older hard-wired types, inject an arbitrary ID store ref.
+        if (getClass().equals(PairwiseIdDataConnectorParser.class)) {
+            builder.addPropertyReference("pairwiseIdStore", config.getAttributeNS(null, "pairwiseIdStoreRef"));
+        }
 
         final List<Element> failoverConnector = ElementSupport.getChildElements(config, 
                 AbstractDataConnectorParser.FAILOVER_DATA_CONNECTOR_ELEMENT_NAME);
         if (failoverConnector != null && !failoverConnector.isEmpty()) {
             if (failoverConnector.size() > 1) {
-                log.warn("{} More than one failover data connector specified, taking the first",
-                        getLogPrefix());                
+                log.warn("{} More than one failover data connector specified, taking the first", getLogPrefix());
             }
             
             final String connectorId = StringSupport.trimOrNull(failoverConnector.get(0).getAttributeNS(null, "ref"));
             log.debug("{} Setting the following failover data connector dependencies: {}", getLogPrefix(), connectorId);
             builder.addPropertyValue("failoverDataConnectorId", connectorId);
         }
-
-        if (config.hasAttributeNS(null, "algorithm")) {
-            builder.addPropertyValue("algorithm", config.getAttributeNS(null, "algorithm"));
-        }
-
-        if (config.hasAttributeNS(null, "encoding")) {
-            builder.addPropertyValue("encoding", config.getAttributeNS(null, "encoding"));
-        }
-
-        final String salt;
-        if (AttributeSupport.hasAttribute(config, new QName("salt"))) {
-            salt = config.getAttributeNS(null, "salt");
-        } else {
-            salt = null;
-        }
-        
-        if (null == salt) {
-            log.debug("{} Generated Attribute: '{}', no salt provided", getLogPrefix(), generatedAttribute);
-        } else {
-            log.debug("{} Generated Attribute: '{}', see TRACE log for the salt value", 
-                    getLogPrefix(), generatedAttribute);
-            log.trace("{} salt: '{}'", getLogPrefix(), salt);
-        }
-
-        builder.addPropertyValue("generatedAttributeId", generatedAttribute);
-        builder.addPropertyValue("salt", salt);
     }
-    // Checkstyle: CyclomaticComplexity|MethodLength ON
 
     /**
-     * return a string which is to be prepended to all log messages.
+     * Return a string which is to be prepended to all log messages.
      * 
      * @return "Attribute Definition: '<definitionID>' :"
      */
@@ -117,4 +108,5 @@ public abstract class BaseComputedIDDataConnectorParser extends BaseResolverPlug
         final StringBuilder builder = new StringBuilder("Data Connector '").append(getDefinitionId()).append("':");
         return builder.toString();
     }
+    
 }
\ No newline at end of file
diff --git a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/impl/StoredIDDataConnectorParser.java b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/impl/StoredIDDataConnectorParser.java
index 2ea9850..ee77c0c 100644
--- a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/impl/StoredIDDataConnectorParser.java
+++ b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/impl/StoredIDDataConnectorParser.java
@@ -18,32 +18,24 @@
 package net.shibboleth.idp.attribute.resolver.spring.dc.impl;
 
 import javax.annotation.Nonnull;
-import javax.sql.DataSource;
 import javax.xml.namespace.QName;
 
-import net.shibboleth.ext.spring.context.FilesystemGenericApplicationContext;
 import net.shibboleth.ext.spring.util.SpringSupport;
+import net.shibboleth.idp.attribute.impl.JDBCPairwiseIdStore;
 import net.shibboleth.idp.attribute.resolver.spring.impl.AttributeResolverNamespaceHandler;
-import net.shibboleth.idp.saml.attribute.resolver.impl.StoredIDDataConnector;
-import net.shibboleth.utilities.java.support.primitive.DeprecationSupport;
-import net.shibboleth.utilities.java.support.primitive.DeprecationSupport.ObjectType;
 import net.shibboleth.utilities.java.support.primitive.StringSupport;
-import net.shibboleth.utilities.java.support.xml.AttributeSupport;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.BeanFactory;
 import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
 import org.springframework.beans.factory.xml.ParserContext;
-import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
-import org.springframework.context.support.GenericApplicationContext;
 import org.w3c.dom.Element;
 
 /**
  * Spring bean definition parser for {@link StoredIDDataConnector}.
  */
-public class StoredIDDataConnectorParser extends BaseComputedIDDataConnectorParser {
+public class StoredIDDataConnectorParser extends ComputedIDDataConnectorParser {
 
     /** Schema type - resolver. */
     @Nonnull public static final QName TYPE_NAME_RESOLVER =
@@ -53,23 +45,30 @@ public class StoredIDDataConnectorParser extends BaseComputedIDDataConnectorPars
     @Nonnull private final Logger log = LoggerFactory.getLogger(StoredIDDataConnectorParser.class);
 
     /** {@inheritDoc} */
-    @Override protected Class<StoredIDDataConnector> getBeanClass(final Element element) {
-        return StoredIDDataConnector.class;
-    }
-
-    /** {@inheritDoc} */
     @Override protected void doParse(@Nonnull final Element config, @Nonnull final ParserContext parserContext,
             @Nonnull final BeanDefinitionBuilder builder) {
         super.doParse(config, parserContext, builder, "storedId");
 
-        log.debug("{} doParse {}", getLogPrefix(), config);
-        final String springResources = AttributeSupport.getAttributeValue(config, new QName("springResources"));
+        builder.addPropertyValue("pairwiseIdStore", doJDBCPairwiseIdStore(config, parserContext));
+    }
+
+    /**
+     * Parse the config and define a bean for a {@link JDBCPairwiseIdStore}.
+     * 
+     * @param config the XML element being parsed
+     * @param parserContext the object encapsulating the current state of the parsing process
+     * @return bean definition for the store object to inject
+     */
+    @Nonnull protected BeanDefinition doJDBCPairwiseIdStore(@Nonnull final Element config,
+            @Nonnull final ParserContext parserContext) {
+        
+        final BeanDefinitionBuilder builder =
+                BeanDefinitionBuilder.genericBeanDefinition(JDBCPairwiseIdStore.class);
+        builder.setInitMethodName("initialize");
+        builder.setDestroyMethodName("destroy");
+
         final String beanDataSource = ManagedConnectionParser.getBeanDataSourceID(config);
-        if (springResources != null) {
-            DeprecationSupport.warnOnce(ObjectType.ATTRIBUTE, "springResources in StoredIDDataConnector",
-                    parserContext.getReaderContext().getResource().getDescription(), "<BeanManagedConnection> element");
-            builder.addPropertyValue("dataSource", getDataSource(springResources.split(";")));
-        } else if (beanDataSource != null) {
+        if (beanDataSource != null) {
             builder.addPropertyReference("dataSource", beanDataSource);
         } else {
             builder.addPropertyValue("dataSource", getv2DataSource(config));
@@ -85,7 +84,7 @@ public class StoredIDDataConnectorParser extends BaseComputedIDDataConnectorPars
         }
 
         if (config.hasAttributeNS(null, "failFast")) {
-            builder.addPropertyValue("failFast",
+            builder.addPropertyValue("verifyDatabase",
                     StringSupport.trimOrNull(config.getAttributeNS(null, "failFast")));
         }
 
@@ -93,37 +92,14 @@ public class StoredIDDataConnectorParser extends BaseComputedIDDataConnectorPars
             builder.addPropertyValue("retryableErrors",
                     SpringSupport.getAttributeValueAsList(config.getAttributeNodeNS(null, "retryableErrors")));
         }
-    }
 
-    /**
-     * Creates a Spring bean factory from the supplied spring resources.
-     * 
-     * @param springResources to load bean definitions from
-     * 
-     * @return bean factory
-     */
-    @Nonnull protected BeanFactory createBeanFactory(@Nonnull final String... springResources) {
-        final GenericApplicationContext ctx = new FilesystemGenericApplicationContext();
-        final XmlBeanDefinitionReader definitionReader = new XmlBeanDefinitionReader(ctx);
-        definitionReader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_XSD);
-        definitionReader.setNamespaceAware(true);
-        definitionReader.loadBeanDefinitions(springResources);
-        ctx.refresh();
-        return ctx.getBeanFactory();
-    }
-
-    /**
-     * Get the dataSource from the configuration.
-     * 
-     * @param springResource location of a spring resource.
-     * @return the DataSource
-     */
-    protected DataSource getDataSource(@Nonnull final String... springResource) {
-        final BeanFactory beanFactory = createBeanFactory(springResource);
-        return beanFactory.getBean(DataSource.class);
+        if (config.hasAttributeNS(null, "salt")) {
+            builder.addPropertyValue("initialValueStore", doComputedPairwiseIdStore(config, parserContext));
+        }
+        
+        return builder.getBeanDefinition();
     }
-
- 
+     
     /**
      * Get the dataSource from a v2 configuration.
      * 
@@ -135,4 +111,5 @@ public class StoredIDDataConnectorParser extends BaseComputedIDDataConnectorPars
         final ManagedConnectionParser parser = new ManagedConnectionParser(config);
         return parser.createDataSource();
     }
+
 }
\ No newline at end of file
diff --git a/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ComputedIDDataConnectorParserTest.java b/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ComputedIDDataConnectorParserTest.java
index 42efc8a..09f9757 100644
--- a/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ComputedIDDataConnectorParserTest.java
+++ b/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ComputedIDDataConnectorParserTest.java
@@ -27,46 +27,51 @@ import org.testng.annotations.Test;
 
 import net.shibboleth.ext.spring.context.FilesystemGenericApplicationContext;
 import net.shibboleth.ext.spring.util.SchemaTypeAwareXMLBeanDefinitionReader;
+import net.shibboleth.idp.attribute.impl.ComputedPairwiseIdStore;
+import net.shibboleth.idp.attribute.impl.ComputedPairwiseIdStore.Encoding;
+import net.shibboleth.idp.attribute.resolver.dc.impl.PairwiseIdDataConnector;
 import net.shibboleth.idp.attribute.resolver.spring.BaseAttributeDefinitionParserTest;
 import net.shibboleth.idp.attribute.resolver.spring.dc.impl.ComputedIDDataConnectorParser;
-import net.shibboleth.idp.saml.attribute.resolver.impl.ComputedIDDataConnector;
-import net.shibboleth.idp.saml.nameid.impl.ComputedPersistentIdGenerationStrategy.Encoding;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 
 /**
- * test for {@link ComputedIDDataConnectorParser}
+ * Test for {@link ComputedIDDataConnectorParser}.
  */
 public class ComputedIDDataConnectorParserTest extends BaseAttributeDefinitionParserTest {
     
     @Test public void withSalt() throws ComponentInitializationException {
-        final ComputedIDDataConnector connector = getDataConnector("resolver/computed.xml", ComputedIDDataConnector.class);
+        final PairwiseIdDataConnector connector = getDataConnector("resolver/computed.xml", PairwiseIdDataConnector.class);
+        final ComputedPairwiseIdStore store = (ComputedPairwiseIdStore) connector.getPairwiseIdStore();
         
         Assert.assertEquals(connector.getId(), "computed");
         Assert.assertEquals(connector.getGeneratedAttributeId(), "jenny");
-        Assert.assertEquals(connector.getSalt(), "abcdefghijklmnopqrst ".getBytes());
+        Assert.assertEquals(store.getSalt(), "abcdefghijklmnopqrst ".getBytes());
         Assert.assertEquals(connector.getSourceAttributeInformation(), "theSourceRemainsTheSame");
-        Assert.assertEquals(connector.getAlgorithm(), "SHA256");
-        Assert.assertEquals(connector.getEncoding(), Encoding.BASE32);
+        Assert.assertEquals(store.getAlgorithm(), "SHA256");
+        Assert.assertEquals(store.getEncoding(), Encoding.BASE32);
 
         Assert.assertTrue(connector.isInitialized());
     }
 
     @Test public void resolverDataConnector() throws ComponentInitializationException {
-        final ComputedIDDataConnector connector = getDataConnector("resolver/computedDataConnector.xml", ComputedIDDataConnector.class);
+        final PairwiseIdDataConnector connector = getDataConnector("resolver/computedDataConnector.xml", PairwiseIdDataConnector.class);
+        final ComputedPairwiseIdStore store = (ComputedPairwiseIdStore) connector.getPairwiseIdStore();
 
         Assert.assertEquals(connector.getId(), "computed");
         Assert.assertEquals(connector.getGeneratedAttributeId(), "jenny");
-        Assert.assertEquals(connector.getSalt(), "abcdefghijklmnopqrst ".getBytes());
+        Assert.assertEquals(store.getSalt(), "abcdefghijklmnopqrst ".getBytes());
         Assert.assertEquals(connector.getSourceAttributeInformation(), "DC/theSourceRemainsTheSame");
 
         Assert.assertTrue(connector.isInitialized());
 }
 
     @Test public void resolverNoSourceDependency() {
-        final ComputedIDDataConnector connector = getDataConnector("resolver/computedNoSource1.xml", ComputedIDDataConnector.class);
+        final PairwiseIdDataConnector connector = getDataConnector("resolver/computedNoSource1.xml", PairwiseIdDataConnector.class);
+        final ComputedPairwiseIdStore store = (ComputedPairwiseIdStore) connector.getPairwiseIdStore();
+        
         Assert.assertEquals(connector.getId(), "computed");
         Assert.assertEquals(connector.getGeneratedAttributeId(), "jenny");
-        Assert.assertEquals(connector.getSalt(), "abcdefghijklmnopqrst ".getBytes());
+        Assert.assertEquals(store.getSalt(), "abcdefghijklmnopqrst ".getBytes());
         Assert.assertEquals(connector.getSourceAttributeInformation(), "theSourceRemainsTheSame");
 
     }
@@ -101,9 +106,11 @@ public class ComputedIDDataConnectorParserTest extends BaseAttributeDefinitionPa
 
         context.refresh();
        
-        final ComputedIDDataConnector connector =  context.getBean(ComputedIDDataConnector.class);
+        final PairwiseIdDataConnector connector =  context.getBean(PairwiseIdDataConnector.class);
+        final ComputedPairwiseIdStore store = (ComputedPairwiseIdStore) connector.getPairwiseIdStore();
         
-        Assert.assertEquals(connector.getSalt(), salt.getBytes());
+        Assert.assertEquals(store.getSalt(), salt.getBytes());
+        Assert.assertTrue(store.isInitialized());
         Assert.assertTrue(connector.isInitialized());
     }
 
diff --git a/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ManagedConnectionTest.java b/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ManagedConnectionTest.java
index 3ede410..b972537 100644
--- a/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ManagedConnectionTest.java
+++ b/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ManagedConnectionTest.java
@@ -40,8 +40,9 @@ import javax.sql.DataSource;
 
 import org.testng.annotations.Test;
 
+import net.shibboleth.idp.attribute.impl.JDBCPairwiseIdStore;
+import net.shibboleth.idp.attribute.resolver.dc.impl.PairwiseIdDataConnector;
 import net.shibboleth.idp.attribute.resolver.spring.BaseAttributeDefinitionParserTest;
-import net.shibboleth.idp.saml.attribute.resolver.impl.StoredIDDataConnector;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 
 /**
@@ -49,13 +50,13 @@ import net.shibboleth.utilities.java.support.component.ComponentInitializationEx
  */
 public class ManagedConnectionTest extends BaseAttributeDefinitionParserTest {
 
-    @Test(enabled=false) public void simple() throws ComponentInitializationException, NamingException {
+    @Test(enabled=true) public void simple() throws ComponentInitializationException, NamingException {
         if (!NamingManager.hasInitialContextFactoryBuilder()) {
             NamingManager.setInitialContextFactoryBuilder(new ContextFactoryBuilder() );
         }
 
-        final  StoredIDDataConnector connector = getDataConnector("resolver/containerManagedConnection.xml", StoredIDDataConnector.class);
-        MyDataSource source = (MyDataSource) connector.getDataSource();
+        final PairwiseIdDataConnector connector = getDataConnector("resolver/containerManagedConnection.xml", PairwiseIdDataConnector.class);
+        MyDataSource source = (MyDataSource) ((JDBCPairwiseIdStore) connector.getPairwiseIdStore()).getDataSource();
         assertEquals(source.getEnvironment().size(), 2);
         assertEquals(source.getEnvironment().get("foo"), "Bar");
         assertEquals(source.getEnvironment().get("yellow"), "green");
@@ -98,115 +99,92 @@ public class ManagedConnectionTest extends BaseAttributeDefinitionParserTest {
 
         /** {@inheritDoc} */
         public Object addToEnvironment(String propName, Object propVal) throws NamingException {
-            // TODO Auto-generated method stub
             return null;
         }
 
         /** {@inheritDoc} */
         public void bind(Name name, Object obj) throws NamingException {
-            // TODO Auto-generated method stub
             
         }
 
         /** {@inheritDoc} */
         public void bind(String name, Object obj) throws NamingException {
-            // TODO Auto-generated method stub
-            
         }
 
         /** {@inheritDoc} */
         public void close() throws NamingException {
-            // TODO Auto-generated method stub
-            
         }
 
         /** {@inheritDoc} */
         public Name composeName(Name name, Name prefix) throws NamingException {
-            // TODO Auto-generated method stub
             return null;
         }
 
         /** {@inheritDoc} */
         public String composeName(String name, String prefix) throws NamingException {
-            // TODO Auto-generated method stub
             return null;
         }
 
         /** {@inheritDoc} */
         public Context createSubcontext(Name name) throws NamingException {
-            // TODO Auto-generated method stub
             return null;
         }
 
         /** {@inheritDoc} */
         public Context createSubcontext(String name) throws NamingException {
-            // TODO Auto-generated method stub
             return null;
         }
 
         /** {@inheritDoc} */
         public void destroySubcontext(Name name) throws NamingException {
-            // TODO Auto-generated method stub
-            
         }
 
         /** {@inheritDoc} */
         public void destroySubcontext(String name) throws NamingException {
-            // TODO Auto-generated method stub
-            
         }
 
         /** {@inheritDoc} */
         public Hashtable<?, ?> getEnvironment() throws NamingException {
-            // TODO Auto-generated method stub
             return null;
         }
 
         /** {@inheritDoc} */
         public String getNameInNamespace() throws NamingException {
-            // TODO Auto-generated method stub
             return null;
         }
 
         /** {@inheritDoc} */
         public NameParser getNameParser(Name name) throws NamingException {
-            // TODO Auto-generated method stub
             return null;
         }
 
         /** {@inheritDoc} */
         public NameParser getNameParser(String name) throws NamingException {
-            // TODO Auto-generated method stub
             return null;
         }
 
         /** {@inheritDoc} */
         public NamingEnumeration<NameClassPair> list(Name name) throws NamingException {
-            // TODO Auto-generated method stub
             return null;
         }
 
         /** {@inheritDoc} */
         public NamingEnumeration<NameClassPair> list(String name) throws NamingException {
-            // TODO Auto-generated method stub
             return null;
         }
 
         /** {@inheritDoc} */
         public NamingEnumeration<Binding> listBindings(Name name) throws NamingException {
-            // TODO Auto-generated method stub
             return null;
         }
 
         /** {@inheritDoc} */
         public NamingEnumeration<Binding> listBindings(String name) throws NamingException {
-            // TODO Auto-generated method stub
             return null;
         }
 
         /** {@inheritDoc} */
         public Object lookup(Name name) throws NamingException {
-            // TODO Auto-generated method stub
             return null;
         }
 
@@ -220,56 +198,41 @@ public class ManagedConnectionTest extends BaseAttributeDefinitionParserTest {
 
         /** {@inheritDoc} */
         public Object lookupLink(Name name) throws NamingException {
-            // TODO Auto-generated method stub
             return null;
         }
 
         /** {@inheritDoc} */
         public Object lookupLink(String name) throws NamingException {
-            // TODO Auto-generated method stub
             return null;
         }
 
         /** {@inheritDoc} */
         public void rebind(Name name, Object obj) throws NamingException {
-            // TODO Auto-generated method stub
-            
         }
 
         /** {@inheritDoc} */
         public void rebind(String name, Object obj) throws NamingException {
-            // TODO Auto-generated method stub
-            
         }
 
         /** {@inheritDoc} */
         public Object removeFromEnvironment(String propName) throws NamingException {
-            // TODO Auto-generated method stub
             return null;
         }
 
         /** {@inheritDoc} */
         public void rename(Name oldName, Name newName) throws NamingException {
-            // TODO Auto-generated method stub
-            
         }
 
         /** {@inheritDoc} */
         public void rename(String oldName, String newName) throws NamingException {
-            // TODO Auto-generated method stub
-            
         }
 
         /** {@inheritDoc} */
         public void unbind(Name name) throws NamingException {
-            // TODO Auto-generated method stub
-            
         }
 
         /** {@inheritDoc} */
         public void unbind(String name) throws NamingException {
-            // TODO Auto-generated method stub
-            
         }
     }
     
diff --git a/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/StoredIDDataConnectorParserTest.java b/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/StoredIDDataConnectorParserTest.java
index fea4f9a..4bd2498 100644
--- a/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/StoredIDDataConnectorParserTest.java
+++ b/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/StoredIDDataConnectorParserTest.java
@@ -30,39 +30,46 @@ import net.shibboleth.ext.spring.config.DurationToLongConverter;
 import net.shibboleth.ext.spring.config.StringToIPRangeConverter;
 import net.shibboleth.ext.spring.config.StringToResourceConverter;
 import net.shibboleth.ext.spring.util.SchemaTypeAwareXMLBeanDefinitionReader;
+import net.shibboleth.idp.attribute.impl.ComputedPairwiseIdStore;
+import net.shibboleth.idp.attribute.impl.JDBCPairwiseIdStore;
 import net.shibboleth.idp.attribute.resolver.ResolverAttributeDefinitionDependency;
+import net.shibboleth.idp.attribute.resolver.dc.impl.PairwiseIdDataConnector;
 import net.shibboleth.idp.attribute.resolver.spring.BaseAttributeDefinitionParserTest;
 import net.shibboleth.idp.attribute.resolver.spring.dc.impl.StoredIDDataConnectorParser;
 import net.shibboleth.idp.attribute.resolver.spring.dc.rdbms.RDBMSDataConnectorParserTest;
-import net.shibboleth.idp.saml.attribute.resolver.impl.StoredIDDataConnector;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 
 /**
- * test for {@link StoredIDDataConnectorParser}
+ * Test for {@link StoredIDDataConnectorParser}.
  */
 public class StoredIDDataConnectorParserTest extends BaseAttributeDefinitionParserTest {
     
-    private void testIt(final StoredIDDataConnector connector) throws ComponentInitializationException {
+    private void testIt(final PairwiseIdDataConnector connector) throws ComponentInitializationException {
+        
+        final JDBCPairwiseIdStore store = (JDBCPairwiseIdStore) connector.getPairwiseIdStore();
+        
         Assert.assertEquals(connector.getId(), "stored");
         Assert.assertEquals(connector.getGeneratedAttributeId(), "jenny");
-        Assert.assertEquals(connector.getTransactionRetries(), 5);
-        Assert.assertEquals(connector.getQueryTimeout(), 5000);
-        Assert.assertEquals(connector.getFailFast(), false);
-        Assert.assertTrue(Arrays.equals(connector.getRetryableErrors().toArray(), new String[]{"25000", "25001"}));
+        Assert.assertEquals(store.getTransactionRetries(), 5);
+        Assert.assertEquals(store.getQueryTimeout(), 5000);
+        Assert.assertEquals(store.getVerifyDatabase(), false);
+        Assert.assertTrue(Arrays.equals(store.getRetryableErrors().toArray(), new String[]{"25000", "25001"}));
         
         connector.initialize();
     }
     
     @Test public void withSalt() throws ComponentInitializationException {
-        final StoredIDDataConnector connector = getDataConnector("resolver/stored.xml", StoredIDDataConnector.class);
-        
+        final PairwiseIdDataConnector connector = getDataConnector("resolver/stored.xml", PairwiseIdDataConnector.class);
+        final JDBCPairwiseIdStore store = (JDBCPairwiseIdStore) connector.getPairwiseIdStore();
+        final ComputedPairwiseIdStore store2 = (ComputedPairwiseIdStore) store.getInitialValueStore();
+
         final ResolverAttributeDefinitionDependency attrib = connector.getAttributeDependencies().iterator().next();
         Assert.assertEquals(attrib.getDependencyPluginId(), "theSourceRemainsTheSame");
-        Assert.assertEquals(connector.getSalt(), "abcdefghijklmnopqrst".getBytes());
+        Assert.assertEquals(store2.getSalt(), "abcdefghijklmnopqrst".getBytes());
         testIt(connector);
     }
 
-    protected StoredIDDataConnector getStoredDataConnector(final String... beanDefinitions) throws IOException {
+    protected PairwiseIdDataConnector getStoredDataConnector(final String... beanDefinitions) throws IOException {
         final GenericApplicationContext context = new GenericApplicationContext();
         setTestContext(context);
         context.setDisplayName("ApplicationContext: " + RDBMSDataConnectorParserTest.class);
@@ -81,22 +88,25 @@ public class StoredIDDataConnectorParserTest extends BaseAttributeDefinitionPars
         beanDefinitionReader.loadBeanDefinitions(beanDefinitions);
         context.refresh();
 
-        return (StoredIDDataConnector) context.getBean(StoredIDDataConnector.class);
+        return (PairwiseIdDataConnector) context.getBean(PairwiseIdDataConnector.class);
     }
 
     @Test public void beanManaged() throws ComponentInitializationException, IOException {
-        final StoredIDDataConnector connector = getStoredDataConnector(DATACONNECTOR_FILE_PATH + "resolver/storedBeanManaged.xml", 
+        final PairwiseIdDataConnector connector = getStoredDataConnector(DATACONNECTOR_FILE_PATH + "resolver/storedBeanManaged.xml", 
                 DATACONNECTOR_FILE_PATH + "rdbms/rdbms-attribute-resolver-spring-context.xml");
+        final JDBCPairwiseIdStore store = (JDBCPairwiseIdStore) connector.getPairwiseIdStore();
+        final ComputedPairwiseIdStore store2 = (ComputedPairwiseIdStore) store.getInitialValueStore();
+        
         final ResolverAttributeDefinitionDependency attrib = connector.getAttributeDependencies().iterator().next();
         Assert.assertEquals(attrib.getDependencyPluginId(), "theSourceRemainsTheSame");
-        Assert.assertEquals(connector.getSalt(), "abcdefghijklmnopqrst".getBytes());
+        Assert.assertEquals(store2.getSalt(), "abcdefghijklmnopqrst".getBytes());
         testIt(connector);
     }
 
 
 
     @Test public void withOutSalt() throws ComponentInitializationException {
-        final StoredIDDataConnector connector = getDataConnector("resolver/storedNoSalt.xml", StoredIDDataConnector.class);
+        final PairwiseIdDataConnector connector = getDataConnector("resolver/storedNoSalt.xml", PairwiseIdDataConnector.class);
         final ResolverAttributeDefinitionDependency attrib = connector.getAttributeDependencies().iterator().next();
         Assert.assertEquals(attrib.getDependencyPluginId(), "theSourceRemainsTheSame");
         testIt(connector);
diff --git a/idp-conf/src/main/resources/system/conf/saml-nameid-system.xml b/idp-conf/src/main/resources/system/conf/saml-nameid-system.xml
index b8e02f5..6f69cda 100644
--- a/idp-conf/src/main/resources/system/conf/saml-nameid-system.xml
+++ b/idp-conf/src/main/resources/system/conf/saml-nameid-system.xml
@@ -28,7 +28,7 @@
     <bean id="shibboleth.SAML2PersistentGenerator" lazy-init="true"
             class="net.shibboleth.idp.saml.nameid.impl.PersistentSAML2NameIDGenerator"
             p:useUnfilteredAttributes="%{idp.persistentId.useUnfilteredAttributes:true}"
-            p:persistentIdGenerator-ref="#{'%{idp.persistentId.generator:shibboleth.ComputedPersistentIdGenerator}'.trim()}">
+            p:persistentIdStore-ref="#{'%{idp.persistentId.store:%{idp.persistentId.generator:shibboleth.ComputedPersistentIdGenerator}}'.trim()}">
         <property name="attributeSourceIds">
             <bean parent="shibboleth.CommaDelimStringArray" c:_0="#{'%{idp.persistentId.sourceAttribute:}'.trim()}" />
         </property>
@@ -58,22 +58,20 @@
         p:dataSealer-ref="shibboleth.DataSealer" />
 
     <bean id="shibboleth.ComputedPersistentIdGenerator" lazy-init="true"
-        class="net.shibboleth.idp.saml.nameid.impl.ComputedPersistentIdGenerationStrategy"
+        class="net.shibboleth.idp.attribute.impl.ComputedPairwiseIdStore"
         p:salt="%{idp.persistentId.salt:}"
         p:encodedSalt="%{idp.persistentId.encodedSalt:}"
         p:exceptionMap="#{getObject('%{idp.persistentId.exceptionMap:shibboleth.ComputedIdExceptionMap}'.trim())}"
         p:algorithm="%{idp.persistentId.algorithm:SHA}"
-        p:encoding="#{ T(net.shibboleth.idp.saml.nameid.impl.ComputedPersistentIdGenerationStrategy.Encoding).%{idp.persistentId.encoding:BASE64} }" />
+        p:encoding="#{ T(net.shibboleth.idp.attribute.impl.ComputedPairwiseIdStore.Encoding).%{idp.persistentId.encoding:BASE64} }" />
 
-    <bean id="shibboleth.StoredPersistentIdGenerator" lazy-init="true"
-        class="net.shibboleth.idp.saml.nameid.impl.StoredPersistentIdGenerationStrategy"
-        p:computedIdStrategy="#{getObject('%{idp.persistentId.computed:shibboleth.ComputedPersistentIdGenerator}'.trim())}"
-        p:IDStore="#{getObject('%{idp.persistentId.store:undefined}'.trim())}"
+    <bean id="shibboleth.StoredPersistentIdGenerator" parent="shibboleth.JDBCPersistentIdStore" lazy-init="true"
         p:dataSource="#{getObject('%{idp.persistentId.dataSource:undefined}'.trim())}" />
     
     <!-- Parent bean for users to configure a custom ID store rather than a data source only. -->
     <bean id="shibboleth.JDBCPersistentIdStore" abstract="true"
-        class="net.shibboleth.idp.saml.nameid.impl.JDBCPersistentIdStoreEx" />
+        class="net.shibboleth.idp.attribute.impl.JDBCPairwiseIdStore"
+        p:initialValueStore="#{getObject('%{idp.persistentId.computed:shibboleth.ComputedPersistentIdGenerator}'.trim())}" />
     
     <!--
     This is the actual service being exposed, and it takes a pair of generators that are
diff --git a/idp-parent/pom.xml b/idp-parent/pom.xml
index bc5099a..2ff2209 100644
--- a/idp-parent/pom.xml
+++ b/idp-parent/pom.xml
@@ -40,6 +40,7 @@
 
         <module>../idp-admin-impl</module>
         <module>../idp-authn-impl</module>
+        <module>../idp-attribute-impl</module>
         <module>../idp-attribute-resolver-impl</module>
         <module>../idp-attribute-filter-impl</module>
         <module>../idp-cas-impl</module>
diff --git a/idp-saml-impl/pom.xml b/idp-saml-impl/pom.xml
index 71e95cc..51acbd8 100644
--- a/idp-saml-impl/pom.xml
+++ b/idp-saml-impl/pom.xml
@@ -46,6 +46,11 @@
         </dependency>
         <dependency>
             <groupId>${project.groupId}</groupId>
+            <artifactId>idp-attribute-impl</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
             <artifactId>idp-profile-api</artifactId>
             <version>${project.version}</version>
         </dependency>
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/attribute/resolver/impl/ComputedIDDataConnector.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/attribute/resolver/impl/ComputedIDDataConnector.java
deleted file mode 100644
index dbc8768..0000000
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/attribute/resolver/impl/ComputedIDDataConnector.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.saml.attribute.resolver.impl;
-
-import java.util.Arrays;
-import java.util.Map;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import net.shibboleth.idp.attribute.IdPAttribute;
-import net.shibboleth.idp.attribute.resolver.ResolutionException;
-import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
-import net.shibboleth.idp.attribute.resolver.context.AttributeResolverWorkContext;
-import net.shibboleth.idp.saml.nameid.impl.ComputedPersistentIdGenerationStrategy;
-import net.shibboleth.idp.saml.nameid.impl.ComputedPersistentIdGenerationStrategy.Encoding;
-import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
-import net.shibboleth.utilities.java.support.component.ComponentSupport;
-
-import org.opensaml.saml.common.SAMLException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Strings;
-
-/**
- * A data connector that delegates generation of IDs to a {@link ComputedPersistentIdGenerationStrategy}.
- */
-public class ComputedIDDataConnector extends AbstractPersistentIdDataConnector {
-
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(ComputedIDDataConnector.class);
-
-    /** Generation strategy for IDs. */
-    @Nullable private ComputedPersistentIdGenerationStrategy idStrategy;
-    
-    /** Constructor. */
-    public ComputedIDDataConnector() {
-        idStrategy = new ComputedPersistentIdGenerationStrategy();
-    }
-    
-    /**
-     * Get the strategy plugin that generates computed IDs.
-     * 
-     * @return strategy for computing IDs
-     */
-    @Nullable public ComputedPersistentIdGenerationStrategy getComputedIdStrategy() {
-        return idStrategy;
-    }
-    
-    /**
-     * Clear the strategy plugin that generates computed IDs.  This is needed
-     * to allow the {@link StoredIDDataConnector}to work if no salt is configured.
-     */
-    @Nullable protected void clearComputedIdStrategy() {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        idStrategy = null;
-    }
-
-    /**
-     * Get the salt used when computing the ID.
-     * 
-     * @return salt used when computing the ID
-     */
-    @Nullable public byte[] getSalt() {
-        if (null == idStrategy) {
-            return null;
-        }
-        return idStrategy.getSalt();
-    }
-
-    /**
-     * Set the salt used when computing the ID.
-     * 
-     * @param salt used when computing the ID
-     */
-    public void setSalt(@Nullable final byte[] salt) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        if (null != salt) {
-            log.debug("{} Salt set (secret is logged at TRACE)", getLogPrefix());
-            log.trace("{} Salt set as bytes to {}", getLogPrefix(), Arrays.toString(salt));
-            idStrategy.setSalt(salt);
-        } else {
-            log.debug("{} Null salt passed, nothing set", getLogPrefix());
-        }
-    }
-    
-    /**
-     * Set the salt used when computing the ID.
-     * 
-     * @param salt used when computing the ID
-     */
-    public void setSalt(@Nullable final String salt) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        if (null != salt) {
-            setSalt(salt.getBytes());
-        } else {
-            log.debug("{} Null salt passed, nothing set", getLogPrefix());
-        }
-    }
-
-    /**
-     * Get the JCE algorithm name of the digest algorithm to use (default is SHA).
-     * 
-     * @return JCE message digest algorithm
-     */
-    @Nullable @NotEmpty public String getAlgorithm() {
-        if (null == idStrategy) {
-            return null;
-        }
-        return idStrategy.getAlgorithm();
-    }
-
-    /**
-     * Set the JCE algorithm name of the digest algorithm to use (default is SHA).
-     * 
-     * @param alg JCE message digest algorithm
-     */
-    public void setAlgorithm(@Nonnull @NotEmpty final String alg) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        idStrategy.setAlgorithm(alg);
-    }
-    
-    /**
-     * Get the post-digest encoding to use.
-     * 
-     * @return encoding
-     * 
-     * @since 3.4.0
-     */
-    @Nullable public Encoding getEncoding() {
-        if (null == idStrategy) {
-            return null;
-        }
-        return idStrategy.getEncoding();
-    }
-
-    /**
-     * Set the post-digest encoding to use.
-     * 
-     * @param enc encoding
-     * 
-     * @since 3.4.0
-     */
-    public void setEncoding(@Nonnull final Encoding enc) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        idStrategy.setEncoding(enc);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected void doInitialize() throws ComponentInitializationException {
-        super.doInitialize();
-        
-        if (null != idStrategy) {
-            idStrategy.initialize();
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected void doDestroy() {
-        if (null != idStrategy) {
-            idStrategy.destroy();
-        }
-        
-        super.doDestroy();
-    }
-    
-    /** {@inheritDoc} */
-    @Override
-    @Nullable protected Map<String, IdPAttribute> doDataConnectorResolve(
-            @Nonnull final AttributeResolutionContext resolutionContext,
-            @Nonnull final AttributeResolverWorkContext workContext) throws ResolutionException {
-
-        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
-        
-        if (null == idStrategy) {
-            log.error("{} Resolve called for seedless stored ID", getLogPrefix());
-            throw new ResolutionException(getLogPrefix() +  " Resolve called for seedless stored ID");
-        }
-
-        final String principalName = resolutionContext.getPrincipal();
-        if (Strings.isNullOrEmpty(principalName)) {
-            log.warn("{} No principal name located, unable to compute ID", getLogPrefix());
-            return null;
-        }
-                
-        final String attributeIssuerId = resolutionContext.getAttributeIssuerID();
-        if (Strings.isNullOrEmpty(attributeIssuerId)) {
-            log.warn("{} No Attribute issuer ID located, unable to compute ID", getLogPrefix());
-            return null;
-        }
-        
-        String attributeRecipientID = resolutionContext.getAttributeRecipientGroupID();
-        if (Strings.isNullOrEmpty(attributeRecipientID)) {
-            attributeRecipientID = resolutionContext.getAttributeRecipientID();
-        }
-        if (Strings.isNullOrEmpty(attributeRecipientID)) {
-            log.warn("{} No Attribute recipient or group ID located, unable to compute ID", getLogPrefix());
-            return null;
-        }
-        
-        final String sourceId = resolveSourceAttribute(workContext);
-        if (Strings.isNullOrEmpty(sourceId)) {
-            return null;
-        }
-        
-        try {
-            return encodeAsAttribute(idStrategy.generate(attributeIssuerId, attributeRecipientID, principalName,
-                    sourceId));
-        } catch (final SAMLException e) {
-            throw new ResolutionException(e);
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/attribute/resolver/impl/StoredIDDataConnector.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/attribute/resolver/impl/StoredIDDataConnector.java
deleted file mode 100644
index bb5aa19..0000000
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/attribute/resolver/impl/StoredIDDataConnector.java
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.saml.attribute.resolver.impl;
-
-import java.util.Collection;
-import java.util.Map;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.sql.DataSource;
-
-import net.shibboleth.idp.attribute.IdPAttribute;
-import net.shibboleth.idp.attribute.resolver.ResolutionException;
-import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
-import net.shibboleth.idp.attribute.resolver.context.AttributeResolverWorkContext;
-import net.shibboleth.idp.saml.nameid.impl.JDBCPersistentIdStoreEx;
-import net.shibboleth.idp.saml.nameid.impl.StoredPersistentIdGenerationStrategy;
-import net.shibboleth.utilities.java.support.annotation.Duration;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonNegative;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
-import net.shibboleth.utilities.java.support.component.ComponentSupport;
-
-import org.opensaml.saml.common.SAMLException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Strings;
-
-/**
- * A data connector that delegates generation of IDs to a {@link StoredPersistentIdGenerationStrategy}
- * that makes use of a {@link JDBCPersistentIdStoreEx}.
- */
-public class StoredIDDataConnector extends ComputedIDDataConnector {
-
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(StoredIDDataConnector.class);
-
-    /** The {@link JDBCPersistentIdStoreEx} used to manage IDs. */
-    @Nonnull private final JDBCPersistentIdStoreEx idStore;
-
-    /** Persistent ID generation mechanism. */
-    @Nonnull private final StoredPersistentIdGenerationStrategy storedIdStrategy;
-    
-    /** Constructor. */
-    public StoredIDDataConnector() {
-        idStore = new JDBCPersistentIdStoreEx();
-        idStore.setVerifyDatabase(false);
-        storedIdStrategy = new StoredPersistentIdGenerationStrategy();
-    }
-    
-    /**
-     * Get the {@link DataSource} used to communicate with the database.
-     * 
-     * @return the {@link DataSource}
-     */
-    @NonnullAfterInit public DataSource getDataSource() {
-        return idStore.getDataSource();
-    }
-
-    /**
-     * Set the {@link DataSource} used to communicate with the database.
-     * 
-     * @param source the {@link DataSource}.
-     */
-    public void setDataSource(@Nonnull final DataSource source) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        idStore.setDataSource(source);
-    }
-
-    /**
-     * Get the data store used to manage stored IDs.
-     * 
-     * @return data store used to manage stored IDs
-     */
-    @Nonnull public JDBCPersistentIdStoreEx getStoredIDStore() {
-        return idStore;
-    }
-
-    /**
-     * Get the error messages to check for classifying a driver error as retryable, generally indicating
-     * a lock violation or duplicate insert that signifies a broken database.
-     * 
-     * @return retryable messages
-     */
-    @Nonnull @NonnullElements public Collection<String> getRetryableErrors() {
-        return idStore.getRetryableErrors();
-    }
-
-    /**
-     * Set the error messages to check for classifying a driver error as retryable, generally indicating
-     * a lock violation or duplicate insert that signifies a broken database.
-     * 
-     * @param errors retryable messages
-     */
-    public void setRetryableErrors(@Nullable @NonnullElements final Collection<String> errors) {
-        idStore.setRetryableErrors(errors);
-    }
-    
-    /**
-     * Get the SQL query timeout.
-     * 
-     * @return the timeout in milliseconds
-     */
-    @Duration @NonNegative public long getQueryTimeout() {
-        return idStore.getQueryTimeout();
-    }
-    
-    /**
-     * Set the SQL query timeout.
-     * 
-     * @param timeout the timeout to set in seconds
-     */
-    @Duration public void setQueryTimeout(@Duration @NonNegative final long timeout) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        idStore.setQueryTimeout(timeout);
-    }
-    
-    /**
-     * Get the number of retries to attempt for a failed transaction.
-     * 
-     * @return number of retries
-     */
-    public int getTransactionRetries() {
-        return idStore.getTransactionRetries();
-    }
-    
-    /**
-     * Set the number of retries to attempt for a failed transaction. Defaults to 3.
-     * 
-     * @param retries the number of retries
-     */
-    public void setTransactionRetries(@NonNegative final int retries) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        idStore.setTransactionRetries(retries);
-    }
-
-    /**
-     * Get whether to allow startup if the database cannot be verified.
-     * 
-     * @return whether to allow startup if the database cannot be verified
-     */
-    public boolean getFailFast() {
-        return idStore.getVerifyDatabase();
-    }
-    
-    /**
-     * Set whether to allow startup if the database cannot be verified.
-     * 
-     * <p>Verification consists not only of a liveness check, but the successful insertion of
-     * a dummy row, a failure to insert a duplicate, and then deletion of the row.</p>
-     * 
-     * @param flag flag to set
-     */
-    public void setFailFast(final boolean flag) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        idStore.setVerifyDatabase(flag);
-    }
-    
-    /** {@inheritDoc} */
-    @Override protected void doInitialize() throws ComponentInitializationException {
-        if (getSalt() == null) {
-            clearComputedIdStrategy();
-        }
-        super.doInitialize();
-
-        idStore.initialize();
-
-        storedIdStrategy.setIDStore(idStore);
-        storedIdStrategy.setComputedIdStrategy(getComputedIdStrategy());
-        storedIdStrategy.initialize();
-    }
-
-    /** {@inheritDoc} */
-    @Override protected void doDestroy() {
-        storedIdStrategy.destroy();
-        idStore.destroy();
-        
-        super.doDestroy();
-    }
-
-    /** {@inheritDoc} */
-    @Override @Nullable protected Map<String, IdPAttribute> doDataConnectorResolve(
-            @Nonnull final AttributeResolutionContext resolutionContext,
-            @Nonnull final AttributeResolverWorkContext workContext) throws ResolutionException {
-
-        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
-
-        final String principalName = resolutionContext.getPrincipal();
-        if (Strings.isNullOrEmpty(principalName)) {
-            log.warn("{} No principal available, skipping ID creation", getLogPrefix());
-            return null;
-        }
-
-        final String sourceId = resolveSourceAttribute(workContext);
-        if (Strings.isNullOrEmpty(sourceId)) {
-            return null;
-        }
-
-        final String attributeIssuerID = resolutionContext.getAttributeIssuerID();
-        if (Strings.isNullOrEmpty(attributeIssuerID)) {
-            log.warn("{} Could not get attribute issuer ID, skipping ID creation", getLogPrefix());
-            return null;
-        }
-
-        String attributeRecipientID = resolutionContext.getAttributeRecipientGroupID();
-        if (Strings.isNullOrEmpty(attributeRecipientID)) {
-            attributeRecipientID = resolutionContext.getAttributeRecipientID();
-        }
-        if (Strings.isNullOrEmpty(attributeRecipientID)) {
-            log.warn("{} Could not get attribute recipient or group ID, skipping ID creation", getLogPrefix());
-            return null;
-        }
-
-        try {
-            return encodeAsAttribute(storedIdStrategy.generate(attributeIssuerID, attributeRecipientID, principalName,
-                    sourceId));
-        } catch (final SAMLException e) {
-            throw new ResolutionException(e);
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/PersistentIdGenerationStrategy.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/PersistentIdGenerationStrategy.java
deleted file mode 100644
index 63a5755..0000000
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/PersistentIdGenerationStrategy.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.saml.nameid.impl;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.saml.common.SAMLException;
-
-import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-
-/**
- * Generates and manages persistent identifiers according to specific strategies.
- */
-public interface PersistentIdGenerationStrategy {
-
-    /**
-     * Get a Persistent ID that corresponds to the inputs.
-     * 
-     * <p>This may be generated directly from the inputs or retrieved from some other source.</p>
-     * 
-     * @param assertingPartyId  the asserting party providing the identifier
-     * @param relyingPartyId    the relying party for whom we're obtaining the identifier
-     * @param principalName     name of the subject
-     * @param sourceId          an underlying identifier for the subject
-     * 
-     * @return  the identifier
-     * @throws SAMLException if an error occurs generating the identifier
-     */
-    @Nonnull @NotEmpty String generate(@Nonnull @NotEmpty final String assertingPartyId,
-            @Nonnull @NotEmpty final String relyingPartyId, @Nonnull @NotEmpty final String principalName,
-            @Nonnull @NotEmpty final String sourceId) throws SAMLException;
-
-}
\ No newline at end of file
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/PersistentIdStoreEx.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/PersistentIdStoreEx.java
deleted file mode 100644
index 1605e0a..0000000
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/PersistentIdStoreEx.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.saml.nameid.impl;
-
-import java.io.IOException;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import net.shibboleth.idp.saml.nameid.PersistentIdEntry;
-import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-
-import org.joda.time.DateTime;
-
-/**
- * Storage and retrieval interface for SAML persistent IDs redesigned to support appropriately
- * atomic behavior.
- */
-public interface PersistentIdStoreEx {
-    
-    /**
-     * Get the {@link PersistentIdEntry} for a previously issued ID triple.
-     * 
-     * @param nameQualifier the NameQualifier value
-     * @param spNameQualifier the SPNameQualifier value
-     * @param persistentId the persistent ID value
-     * 
-     * @return {@link PersistentIdEntry} for the given inputs or null if none exists
-     * @throws IOException if an error occurs accessing the store
-     */
-    @Nullable PersistentIdEntry getByIssuedValue(@Nonnull @NotEmpty final String nameQualifier,
-            @Nonnull @NotEmpty final String spNameQualifier, @Nonnull @NotEmpty final String persistentId)
-                    throws IOException;
-
-// Checkstyle: ParameterNumber OFF
-    /**
-     * Get the {@link PersistentIdEntry} for a given subject and audience, creating one if allowable
-     * and necessary.
-     * 
-     * @param nameQualifier the NameQualifier value
-     * @param spNameQualifier the SPNameQualifier value
-     * @param sourceId source attribute underlying the persistent ID
-     * @param principal principal name of subject (may or may not be the same as the sourceId)
-     * @param allowCreate whether it's permissible to establish/issue a new identifier
-     * @param computedIdStrategy optional source of initial computed IDs for compatibilty with that mechanism
-     * 
-     * @return {@link PersistentIdEntry} for the given inputs, or null if none exists and allowCreate is false
-     * @throws IOException if an error occurs accessing the store
-     */
-    @Nullable PersistentIdEntry getBySourceValue(@Nonnull @NotEmpty final String nameQualifier,
-            @Nonnull @NotEmpty final String spNameQualifier, @Nonnull @NotEmpty final String sourceId,
-            @Nonnull @NotEmpty final String principal, final boolean allowCreate,
-            @Nullable final ComputedPersistentIdGenerationStrategy computedIdStrategy) throws IOException;
-// Checkstyle: ParameterNumber ON
-    
-    /**
-     * Deactivate/revoke a persistent ID.
-     * 
-     * @param nameQualifier the NameQualifier value
-     * @param spNameQualifier the SPNameQualifier value
-     * @param persistentId ID to deactivate
-     * @param deactivation deactivation time (if null the current time is used)
-     * 
-     * @throws IOException if there is an error updating the store
-     */
-    void deactivate(@Nonnull @NotEmpty final String nameQualifier, @Nonnull @NotEmpty final String spNameQualifier,
-            @Nonnull @NotEmpty final String persistentId, @Nullable final DateTime deactivation) throws IOException;
-
-    /**
-     * Attach an SPProvidedID value to an existing entry.
-     * 
-     * @param nameQualifier the NameQualifier value
-     * @param spNameQualifier the SPNameQualifier value
-     * @param persistentId ID to deactivate
-     * @param spProvidedId the value to attach
-     * 
-     * @throws IOException if there is an error updating the store
-     */
-    void attach(@Nonnull @NotEmpty final String nameQualifier, @Nonnull @NotEmpty final String spNameQualifier,
-            @Nonnull @NotEmpty final String persistentId, @Nonnull @NotEmpty final String spProvidedId)
-                    throws IOException;
-    
-}
\ No newline at end of file
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/PersistentSAML2NameIDGenerator.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/PersistentSAML2NameIDGenerator.java
index e223e29..9db0aaf 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/PersistentSAML2NameIDGenerator.java
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/PersistentSAML2NameIDGenerator.java
@@ -17,6 +17,7 @@
 
 package net.shibboleth.idp.saml.nameid.impl;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -25,12 +26,16 @@ import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
+import javax.sql.DataSource;
 
 import net.shibboleth.idp.attribute.IdPAttribute;
 import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.PairwiseId;
+import net.shibboleth.idp.attribute.PairwiseIdStore;
 import net.shibboleth.idp.attribute.ScopedStringAttributeValue;
 import net.shibboleth.idp.attribute.StringAttributeValue;
 import net.shibboleth.idp.attribute.context.AttributeContext;
+import net.shibboleth.idp.attribute.impl.JDBCPairwiseIdStore;
 import net.shibboleth.idp.authn.context.SubjectContext;
 import net.shibboleth.idp.profile.context.RelyingPartyContext;
 import net.shibboleth.idp.profile.context.navigate.RelyingPartyIdLookupFunction;
@@ -65,16 +70,19 @@ public class PersistentSAML2NameIDGenerator extends AbstractSAML2NameIDGenerator
     @Nonnull private final Logger log = LoggerFactory.getLogger(PersistentSAML2NameIDGenerator.class);
 
     /** Strategy function to lookup SubjectContext. */
-    @Nonnull private Function<ProfileRequestContext, SubjectContext> subjectContextLookupStrategy;
+    @Nonnull private Function<ProfileRequestContext,SubjectContext> subjectContextLookupStrategy;
 
     /** Strategy function to lookup AttributeContext. */
-    @Nonnull private Function<ProfileRequestContext, AttributeContext> attributeContextLookupStrategy;
+    @Nonnull private Function<ProfileRequestContext,AttributeContext> attributeContextLookupStrategy;
 
     /** Attribute(s) to use as an identifier source. */
     @Nonnull @NonnullElements private List<String> attributeSourceIds;
 
-    /** Generation strategy for IDs. */
-    @NonnullAfterInit private PersistentIdGenerationStrategy persistentIdStrategy;
+    /** Store for IDs. */
+    @NonnullAfterInit private PairwiseIdStore pidStore;
+    
+    /** A DataSource to auto-provision a {@link JDBCPairwiseIdStore} instance. */
+    @Nullable private DataSource dataSource;
 
     /** Predicate to select whether to look at filtered or unfiltered attributes. */
     private boolean useUnfilteredAttributes;
@@ -98,7 +106,7 @@ public class PersistentSAML2NameIDGenerator extends AbstractSAML2NameIDGenerator
      * @param strategy lookup function to use
      */
     public void setSubjectContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, SubjectContext> strategy) {
+            @Nonnull final Function<ProfileRequestContext,SubjectContext> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
 
         subjectContextLookupStrategy = Constraint.isNotNull(strategy, "SubjectContext lookup strategy cannot be null");
@@ -110,7 +118,7 @@ public class PersistentSAML2NameIDGenerator extends AbstractSAML2NameIDGenerator
      * @param strategy lookup function to use
      */
     public void setAttributeContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, AttributeContext> strategy) {
+            @Nonnull final Function<ProfileRequestContext,AttributeContext> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
 
         attributeContextLookupStrategy =
@@ -130,17 +138,29 @@ public class PersistentSAML2NameIDGenerator extends AbstractSAML2NameIDGenerator
     }
 
     /**
-     * Set the generation strategy for the persistent ID.
+     * Set a {@link PairwiseIdStore} to use.
      * 
-     * @param strategy generation strategy
+     * @param store the id store
      */
-    public void setPersistentIdGenerator(@Nonnull final PersistentIdGenerationStrategy strategy) {
+    public void setPersistentIdStore(@Nullable final PairwiseIdStore store) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
-        persistentIdStrategy = Constraint.isNotNull(strategy, "PersistentIdGenerationStrategy cannot be null");
+        
+        pidStore = store;
     }
 
     /**
+     * Set a data source to inject into an auto-provisioned instance of {@link JDBCPairwiseIdStore}
+     * to use as the store.
+     * 
+     * @param source data source
+     */
+    public void setDataSource(@Nullable final DataSource source) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        dataSource = source;
+    }
+    
+    /**
      * Set whether to source the input attributes from the unfiltered set.
      * 
      * <p>Defaults to true, since the input is not directly exposed.</p>
@@ -155,11 +175,23 @@ public class PersistentSAML2NameIDGenerator extends AbstractSAML2NameIDGenerator
     @Override protected void doInitialize() throws ComponentInitializationException {
         super.doInitialize();
 
-        if (persistentIdStrategy == null) {
-            throw new ComponentInitializationException("PersistentIdGenerationStrategy cannot be null");
-        } else if (attributeSourceIds.isEmpty()) {
+        if (attributeSourceIds.isEmpty()) {
             throw new ComponentInitializationException("Attribute source ID list cannot be empty");
         }
+
+        if (null == pidStore) {
+            if (dataSource != null) {
+                log.debug("Creating JDBCPersistentStoreEx instance around supplied DataSource");
+                final JDBCPairwiseIdStore newStore = new JDBCPairwiseIdStore();
+                newStore.setDataSource(dataSource);
+                newStore.initialize();
+                pidStore = newStore;
+            }
+            
+            if (null == pidStore) {
+                throw new ComponentInitializationException("PairwiseIdStore cannot be null");
+            }
+        }
     }
 
     // Checkstyle: CyclomaticComplexity|MethodLength OFF
@@ -167,7 +199,7 @@ public class PersistentSAML2NameIDGenerator extends AbstractSAML2NameIDGenerator
     @Override @Nullable protected String getIdentifier(@Nonnull final ProfileRequestContext profileRequestContext)
             throws SAMLException {
 
-        Function<ProfileRequestContext, String> lookup = getDefaultIdPNameQualifierLookupStrategy();
+        Function<ProfileRequestContext,String> lookup = getDefaultIdPNameQualifierLookupStrategy();
         final String responderId = lookup != null ? lookup.apply(profileRequestContext) : null;
         if (responderId == null) {
             log.debug("No responder identifier, can't generate persistent ID");
@@ -208,24 +240,35 @@ public class PersistentSAML2NameIDGenerator extends AbstractSAML2NameIDGenerator
                 continue;
             }
 
+            PairwiseId pid = new PairwiseId();
+            pid.setIssuerEntityID(responderId);
+            pid.setRecipientEntityID(relyingPartyId);
+            pid.setPrincipalName(subjectCtx.getPrincipalName());
+            
             final List<IdPAttributeValue<?>> values = attribute.getValues();
             for (final IdPAttributeValue value : values) {
-                if (value instanceof ScopedStringAttributeValue) {
-                    log.debug("Generating persistent NameID from Scoped String-valued attribute {}", sourceId);
-                    return persistentIdStrategy.generate(responderId, relyingPartyId, subjectCtx.getPrincipalName(),
-                            ((ScopedStringAttributeValue) value).getValue() + '@'
-                                    + ((ScopedStringAttributeValue) value).getScope());
-                } else if (value instanceof StringAttributeValue) {
-                    // Check for all whitespace, but don't trim the value used.
-                    if (StringSupport.trimOrNull((String) value.getValue()) == null) {
-                        log.debug("Skipping all-whitespace string value");
-                        continue;
+                try {
+                    if (value instanceof ScopedStringAttributeValue) {
+                        log.debug("Generating persistent NameID from Scoped String-valued attribute {}", sourceId);
+                        pid.setSourceSystemId(((ScopedStringAttributeValue) value).getValue() + '@'
+                                + ((ScopedStringAttributeValue) value).getScope());
+                        pid = pidStore.getBySourceValue(pid, true);
+                        return pid.getPairwiseId();
+                    } else if (value instanceof StringAttributeValue) {
+                        // Check for all whitespace, but don't trim the value used.
+                        if (StringSupport.trimOrNull((String) value.getValue()) == null) {
+                            log.debug("Skipping all-whitespace string value");
+                            continue;
+                        }
+                        log.debug("Generating persistent NameID from String-valued attribute {}", sourceId);
+                        pid.setSourceSystemId((String) value.getValue());
+                        pid = pidStore.getBySourceValue(pid, true);
+                        return pid.getPairwiseId();
+                    } else {
+                        log.info("Unrecognized attribute value type: {}", value.getClass().getName());
                     }
-                    log.debug("Generating persistent NameID from String-valued attribute {}", sourceId);
-                    return persistentIdStrategy.generate(responderId, relyingPartyId, subjectCtx.getPrincipalName(),
-                            (String) value.getValue());
-                } else {
-                    log.info("Unrecognized attribute value type: {}", value.getClass().getName());
+                } catch (final IOException e) {
+                    throw new SAMLException(e);
                 }
             }
         }
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/StoredPersistentIdDecoder.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/StoredPersistentIdDecoder.java
index 9bac79f..41d6c70 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/StoredPersistentIdDecoder.java
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/StoredPersistentIdDecoder.java
@@ -27,10 +27,12 @@ import org.opensaml.saml.saml2.core.NameID;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import net.shibboleth.idp.attribute.DurablePairwiseIdStore;
+import net.shibboleth.idp.attribute.PairwiseId;
+import net.shibboleth.idp.attribute.impl.JDBCPairwiseIdStore;
 import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
 import net.shibboleth.idp.saml.nameid.NameDecoderException;
 import net.shibboleth.idp.saml.nameid.NameIDDecoder;
-import net.shibboleth.idp.saml.nameid.PersistentIdEntry;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.component.AbstractIdentifiableInitializableComponent;
@@ -38,8 +40,8 @@ import net.shibboleth.utilities.java.support.component.ComponentInitializationEx
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 
 /**
- * An abstract action which contains the logic to decode SAML persistent IDs that are managed with a store.
- * This reverses the work done by {@link StoredPersistentIdGenerationStrategy}.
+ * An abstract decoder which contains the logic to decode SAML persistent IDs that are managed with a
+ * {@link DurablePairwiseIdStore}.
  */
 public class StoredPersistentIdDecoder extends AbstractIdentifiableInitializableComponent implements NameIDDecoder {
 
@@ -47,27 +49,27 @@ public class StoredPersistentIdDecoder extends AbstractIdentifiableInitializable
     @Nonnull private final Logger log = LoggerFactory.getLogger(StoredPersistentIdDecoder.class);
 
     /** Updated version of persistent identifier data store layer. */
-    @NonnullAfterInit private PersistentIdStoreEx pidStore;
+    @NonnullAfterInit private DurablePairwiseIdStore pidStore;
 
-    /** A DataSource to auto-provision a {@link JDBCPersistentIdStoreEx} instance. */
+    /** A DataSource to auto-provision a {@link JDBCPairwiseIdStore} instance. */
     @Nullable private DataSource dataSource;
     
     /**
-     * Set a data store to use.
+     * Set a {@link DurablePairwiseIdStore} to use.
      * 
-     * @param store the data store
+     * @param store the id store
      */
-    public void setPersistentIdStore(@Nullable final PersistentIdStoreEx store) {
+    public void setPersistentIdStore(@Nullable final DurablePairwiseIdStore store) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         
-        pidStore = (PersistentIdStoreEx) store;
+        pidStore = store;
     }
 
     /**
-     * Set a data source to inject into an auto-provisioned instance of {@link JDBCPersistentIdStoreEx}
-     * to use as the storage strategy.
+     * Set a data source to inject into an auto-provisioned instance of {@link JDBCPairwiseIdStore}
+     * to use as the store.
      * 
-     * @param source the data source
+     * @param source data source
      */
     public void setDataSource(@Nullable final DataSource source) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
@@ -81,8 +83,8 @@ public class StoredPersistentIdDecoder extends AbstractIdentifiableInitializable
     
         if (null == pidStore) {
             if (dataSource != null) {
-                log.debug("Creating JDBCPersistentStoreEx instance around supplied DataSource");
-                final JDBCPersistentIdStoreEx newStore = new JDBCPersistentIdStoreEx();
+                log.debug("Creating JDBCPairwiseIdStore instance around supplied DataSource");
+                final JDBCPairwiseIdStore newStore = new JDBCPairwiseIdStore();
                 // Don't validate the database because this side is just reading data.
                 newStore.setVerifyDatabase(false);
                 newStore.setDataSource(dataSource);
@@ -91,7 +93,7 @@ public class StoredPersistentIdDecoder extends AbstractIdentifiableInitializable
             }
             
             if (null == pidStore) {
-                throw new ComponentInitializationException("PersistentIdStore cannot be null");
+                throw new ComponentInitializationException("PairwiseIdStore cannot be null");
             }
         }
     }
@@ -122,12 +124,16 @@ public class StoredPersistentIdDecoder extends AbstractIdentifiableInitializable
         }
         
         try {
-            final PersistentIdEntry entry = pidStore.getByIssuedValue(issuerID, recipientID, nameID.getValue());
-            if (entry == null || entry.getPrincipalName() == null) {
+            PairwiseId pid = new PairwiseId();
+            pid.setIssuerEntityID(issuerID);
+            pid.setRecipientEntityID(recipientID);
+            pid.setPairwiseId(nameID.getValue());
+            pid = pidStore.getByIssuedValue(pid);
+            if (pid == null || pid.getPrincipalName() == null) {
                 log.info("No entry found for persistent ID {}", nameID.getValue());
                 return null;
             }
-            return entry.getPrincipalName();
+            return pid.getPrincipalName();
         } catch (final IOException e) {
             log.error("I/O error looking up persistent ID", e);
             return null;
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/StoredPersistentIdGenerationStrategy.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/StoredPersistentIdGenerationStrategy.java
deleted file mode 100644
index e8ea9b4..0000000
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/StoredPersistentIdGenerationStrategy.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.saml.nameid.impl;
-
-import java.io.IOException;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.sql.DataSource;
-
-import net.shibboleth.idp.saml.nameid.PersistentIdEntry;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
-import net.shibboleth.utilities.java.support.component.ComponentSupport;
-import net.shibboleth.utilities.java.support.primitive.StringSupport;
-
-import org.opensaml.saml.common.SAMLException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Manages persistent IDs via a {@link PersistentIdStore}, generating them either randomly or via a
- * {@link ComputedPersistentIdGenerationStrategy} (for compatibility with existing data).
- */
-public class StoredPersistentIdGenerationStrategy extends AbstractInitializableComponent
-        implements PersistentIdGenerationStrategy {
-
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(StoredPersistentIdGenerationStrategy.class);
-
-    /** Updated version of persistent identifier data store layer. */
-    @NonnullAfterInit private PersistentIdStoreEx pidStore;
-
-    /** A DataSource to auto-provision a {@link JDBCPersistentIdStoreEx} instance. */
-    @Nullable private DataSource dataSource;
-    
-    /** Optional generator of computed ID values. */
-    @Nullable private ComputedPersistentIdGenerationStrategy computedIdStrategy;
-
-    /**
-     * Set a {@link PersistentIdStoreEx} used to store the IDs.
-     * 
-     * @param store the ID store to use
-     */
-    public void setIDStore(@Nullable final PersistentIdStoreEx store) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        pidStore = (PersistentIdStoreEx) store;
-    }
-    
-    /**
-     * Set a data source to inject into an auto-provisioned instance of {@link JDBCPersistentIdStoreEx}
-     * to use as the storage strategy.
-     * 
-     * @param source the data source
-     */
-    public void setDataSource(@Nullable final DataSource source) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        dataSource = source;
-    }
-    
-    /**
-     * Set a strategy to use to compute IDs for the first time.
-     * 
-     * @param strategy  computed ID strategy
-     */
-    public void setComputedIdStrategy(@Nullable final ComputedPersistentIdGenerationStrategy strategy) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        computedIdStrategy = strategy;
-    }
-    
-    /** {@inheritDoc} */
-    @Override protected void doInitialize() throws ComponentInitializationException {
-        super.doInitialize();
-
-        if (null == pidStore) {
-            if (dataSource != null) {
-                log.debug("Creating JDBCPersistentStoreEx instance around supplied DataSource");
-                final JDBCPersistentIdStoreEx newStore = new JDBCPersistentIdStoreEx();
-                newStore.setDataSource(dataSource);
-                newStore.initialize();
-                pidStore = newStore;
-            }
-            
-            if (null == pidStore) {
-                throw new ComponentInitializationException("PersistentIdStore cannot be null");
-            }
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    @Nonnull @NotEmpty public String generate(@Nonnull @NotEmpty final String assertingPartyId,
-            @Nonnull @NotEmpty final String relyingPartyId, @Nonnull @NotEmpty final String principalName,
-            @Nonnull @NotEmpty final String sourceId) throws SAMLException {
-        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
-        
-        try {
-            final PersistentIdEntry idEntry = pidStore.getBySourceValue(assertingPartyId, relyingPartyId, sourceId,
-                    principalName, true, computedIdStrategy);
-            if (idEntry == null) {
-                log.debug("No persistent ID returned from storage for '{}'", principalName);
-                throw new SAMLException("No persistent ID returned from storage");
-            }
-            
-            log.debug("Obtained persistent ID entry: {}", idEntry);
-    
-            final String pid = StringSupport.trimOrNull(idEntry.getPersistentId());
-            if (null == pid) {
-                log.debug("Returned persistent ID was null");
-                throw new SAMLException("Returned persistent ID was null");
-            }
-    
-            return pid;
-        } catch (final IOException e) {
-            log.debug("ID storage error obtaining persistent identifier", e);
-            throw new SAMLException("ID storage error obtaining persistent identifier", e);
-        }
-    }
-    
-}
\ No newline at end of file
diff --git a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/nameid/impl/PersistentSAML2NameIDGeneratorTest.java b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/nameid/impl/PersistentSAML2NameIDGeneratorTest.java
index efc669d..8dd787d 100644
--- a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/nameid/impl/PersistentSAML2NameIDGeneratorTest.java
+++ b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/nameid/impl/PersistentSAML2NameIDGeneratorTest.java
@@ -22,21 +22,21 @@ import java.util.Collections;
 import javax.sql.DataSource;
 
 import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.PairwiseId;
 import net.shibboleth.idp.attribute.StringAttributeValue;
 import net.shibboleth.idp.attribute.context.AttributeContext;
+import net.shibboleth.idp.attribute.impl.ComputedPairwiseIdStore;
+import net.shibboleth.idp.attribute.impl.JDBCPairwiseIdStore;
 import net.shibboleth.idp.authn.context.SubjectContext;
 import net.shibboleth.idp.profile.RequestContextBuilder;
 import net.shibboleth.idp.profile.context.RelyingPartyContext;
 import net.shibboleth.idp.saml.impl.TestSources;
-import net.shibboleth.idp.saml.nameid.impl.ComputedPersistentIdGenerationStrategy.Encoding;
 import net.shibboleth.idp.testing.DatabaseTestingSupport;
-import net.shibboleth.utilities.java.support.codec.Base64Support;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 
 import org.opensaml.core.OpenSAMLInitBaseTestCase;
 import org.opensaml.core.xml.util.XMLObjectSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
-import org.opensaml.saml.common.SAMLException;
 import org.opensaml.saml.saml2.core.AuthnRequest;
 import org.opensaml.saml.saml2.core.NameID;
 import org.opensaml.saml.saml2.core.NameIDPolicy;
@@ -54,14 +54,8 @@ public class PersistentSAML2NameIDGeneratorTest extends OpenSAMLInitBaseTestCase
     /** Value calculated using V2 version. DO NOT CHANGE WITHOUT TESTING AGAINST 2.0 */
     private static final String RESULT = "Vl6z6K70iLc4AuBoNeb59Dj1rGw=";
 
-    private static final String RESULT2 = "kLyH1uEvYigEvg1ZLh/QXeW1VAs=";
-
-    private static final String B32RESULT = "KZPLH2FO6SELOOAC4BUDLZXZ6Q4PLLDM";
-
     private static final byte salt[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
 
-    private static final String salt2 = "thisisaspecialsalt";
-
     public static final String INIT_FILE = "/net/shibboleth/idp/saml/impl/nameid/StoredIdStore.sql";
     public static final String DELETE_FILE = "/net/shibboleth/idp/saml/impl/nameid/DeleteStore.sql";
     
@@ -90,28 +84,18 @@ public class PersistentSAML2NameIDGeneratorTest extends OpenSAMLInitBaseTestCase
     
     @Test(expectedExceptions = ComponentInitializationException.class)
     public void testInvalidConfig() throws ComponentInitializationException {
-        final ComputedPersistentIdGenerationStrategy strategy = new ComputedPersistentIdGenerationStrategy();
+        final ComputedPairwiseIdStore strategy = new ComputedPairwiseIdStore();
         strategy.setSalt(salt);
         strategy.initialize();
         
         generator.initialize();
-        generator.setPersistentIdGenerator(strategy);
+        generator.setPersistentIdStore(strategy);
         generator.initialize();
     }
 
     @Test
-    public void testSaltSetters() throws ComponentInitializationException {
-        final ComputedPersistentIdGenerationStrategy strategy = new ComputedPersistentIdGenerationStrategy();
-        strategy.setSalt(salt);
-        Assert.assertEquals(salt, strategy.getSalt());
-        
-        strategy.setEncodedSalt(Base64Support.encode(salt, false));
-        Assert.assertEquals(salt, strategy.getSalt());
-    }
-
-    @Test
     public void testNoResponderId() throws Exception {
-        generator.setPersistentIdGenerator(new ComputedPersistentIdGenerationStrategy());
+        generator.setPersistentIdStore(new ComputedPairwiseIdStore());
         generator.setAttributeSourceIds(Collections.singletonList("SOURCE"));
         generator.initialize();
         
@@ -120,7 +104,7 @@ public class PersistentSAML2NameIDGeneratorTest extends OpenSAMLInitBaseTestCase
 
     @Test
     public void testNoRequesterId() throws Exception {
-        generator.setPersistentIdGenerator(new ComputedPersistentIdGenerationStrategy());
+        generator.setPersistentIdStore(new ComputedPairwiseIdStore());
         generator.setAttributeSourceIds(Collections.singletonList("SOURCE"));
         generator.initialize();
         
@@ -131,7 +115,7 @@ public class PersistentSAML2NameIDGeneratorTest extends OpenSAMLInitBaseTestCase
     
     @Test
     public void testNoSubject() throws Exception {
-        generator.setPersistentIdGenerator(new ComputedPersistentIdGenerationStrategy());
+        generator.setPersistentIdStore(new ComputedPairwiseIdStore());
         generator.setAttributeSourceIds(Collections.singletonList("SOURCE"));
         generator.initialize();
         
@@ -140,7 +124,7 @@ public class PersistentSAML2NameIDGeneratorTest extends OpenSAMLInitBaseTestCase
 
     @Test
     public void testNoSource() throws Exception {
-        generator.setPersistentIdGenerator(new ComputedPersistentIdGenerationStrategy());
+        generator.setPersistentIdStore(new ComputedPairwiseIdStore());
         generator.setAttributeSourceIds(Collections.singletonList("SOURCE"));
         generator.initialize();
         
@@ -151,80 +135,14 @@ public class PersistentSAML2NameIDGeneratorTest extends OpenSAMLInitBaseTestCase
                 Collections.singleton(new IdPAttribute("SOURCE")));
         Assert.assertNull(generator.generate(prc, NameID.PERSISTENT));
     }
-
-    @Test(expectedExceptions=SAMLException.class)
-    public void testRevoked() throws Exception {
-        final ComputedPersistentIdGenerationStrategy strategy = new ComputedPersistentIdGenerationStrategy();
-        strategy.setSalt(salt);
-        strategy.setExceptionMap(Collections.singletonMap("foo",
-                Collections.<String,String>singletonMap(TestSources.SP_ENTITY_ID, null)));
-        strategy.initialize();
-
-        generator.setPersistentIdGenerator(strategy);
-        generator.setAttributeSourceIds(Collections.singletonList("SOURCE"));
-        generator.initialize();
-        
-        prc.getSubcontext(SubjectContext.class, true).setPrincipalName("foo");
-        Assert.assertNull(generator.generate(prc, NameID.PERSISTENT));
-        
-        final IdPAttribute source = new IdPAttribute("SOURCE");
-        source.setValues(Collections.singleton(new StringAttributeValue(TestSources.COMMON_ATTRIBUTE_VALUE_STRING)));
-        prc.getSubcontext(RelyingPartyContext.class).getSubcontext(AttributeContext.class, true).setUnfilteredIdPAttributes(
-                Collections.singleton(source));
-        generator.generate(prc, NameID.PERSISTENT);
-    }
-    
-    @Test(expectedExceptions=SAMLException.class)
-    public void testRevokedWildcardRP() throws Exception {
-        final ComputedPersistentIdGenerationStrategy strategy = new ComputedPersistentIdGenerationStrategy();
-        strategy.setSalt(salt);
-        strategy.setExceptionMap(Collections.singletonMap("foo",
-                Collections.<String,String>singletonMap(ComputedPersistentIdGenerationStrategy.WILDCARD_OVERRIDE, null)));
-        strategy.initialize();
-
-        generator.setPersistentIdGenerator(strategy);
-        generator.setAttributeSourceIds(Collections.singletonList("SOURCE"));
-        generator.initialize();
-        
-        prc.getSubcontext(SubjectContext.class, true).setPrincipalName("foo");
-        Assert.assertNull(generator.generate(prc, NameID.PERSISTENT));
-        
-        final IdPAttribute source = new IdPAttribute("SOURCE");
-        source.setValues(Collections.singleton(new StringAttributeValue(TestSources.COMMON_ATTRIBUTE_VALUE_STRING)));
-        prc.getSubcontext(RelyingPartyContext.class).getSubcontext(AttributeContext.class, true).setUnfilteredIdPAttributes(
-                Collections.singleton(source));
-        generator.generate(prc, NameID.PERSISTENT);
-    }
-    
-    @Test(expectedExceptions=SAMLException.class)
-    public void testRevokedWildcardUser() throws Exception {
-        final ComputedPersistentIdGenerationStrategy strategy = new ComputedPersistentIdGenerationStrategy();
-        strategy.setSalt(salt);
-        strategy.setExceptionMap(Collections.singletonMap(ComputedPersistentIdGenerationStrategy.WILDCARD_OVERRIDE,
-                Collections.<String,String>singletonMap(TestSources.SP_ENTITY_ID, null)));
-        strategy.initialize();
-
-        generator.setPersistentIdGenerator(strategy);
-        generator.setAttributeSourceIds(Collections.singletonList("SOURCE"));
-        generator.initialize();
-        
-        prc.getSubcontext(SubjectContext.class, true).setPrincipalName("foo");
-        Assert.assertNull(generator.generate(prc, NameID.PERSISTENT));
-        
-        final IdPAttribute source = new IdPAttribute("SOURCE");
-        source.setValues(Collections.singleton(new StringAttributeValue(TestSources.COMMON_ATTRIBUTE_VALUE_STRING)));
-        prc.getSubcontext(RelyingPartyContext.class).getSubcontext(AttributeContext.class, true).setUnfilteredIdPAttributes(
-                Collections.singleton(source));
-        generator.generate(prc, NameID.PERSISTENT);
-    }
     
     @Test
     public void testComputedId() throws Exception {
-        final ComputedPersistentIdGenerationStrategy strategy = new ComputedPersistentIdGenerationStrategy();
+        final ComputedPairwiseIdStore strategy = new ComputedPairwiseIdStore();
         strategy.setSalt(salt);
         strategy.initialize();
 
-        generator.setPersistentIdGenerator(strategy);
+        generator.setPersistentIdStore(strategy);
         generator.setAttributeSourceIds(Collections.singletonList("SOURCE"));
         generator.initialize();
         
@@ -244,89 +162,32 @@ public class PersistentSAML2NameIDGeneratorTest extends OpenSAMLInitBaseTestCase
     }
     
     @Test
-    public void testComputedIdOverride() throws Exception {
-        final ComputedPersistentIdGenerationStrategy strategy = new ComputedPersistentIdGenerationStrategy();
-        strategy.setSalt(salt);
-        strategy.setExceptionMap(Collections.singletonMap(ComputedPersistentIdGenerationStrategy.WILDCARD_OVERRIDE,
-                Collections.<String,String>singletonMap(TestSources.SP_ENTITY_ID, salt2)));
-        strategy.initialize();
-
-        generator.setPersistentIdGenerator(strategy);
-        generator.setAttributeSourceIds(Collections.singletonList("SOURCE"));
-        generator.initialize();
-        
-        prc.getSubcontext(SubjectContext.class, true).setPrincipalName("foo");
-        Assert.assertNull(generator.generate(prc, NameID.PERSISTENT));
-        
-        final IdPAttribute source = new IdPAttribute("SOURCE");
-        source.setValues(Collections.singleton(new StringAttributeValue(TestSources.COMMON_ATTRIBUTE_VALUE_STRING)));
-        prc.getSubcontext(RelyingPartyContext.class).getSubcontext(AttributeContext.class, true).setUnfilteredIdPAttributes(
-                Collections.singleton(source));
-        final NameID id = generator.generate(prc, NameID.PERSISTENT);
-        Assert.assertNotNull(id);
-        Assert.assertEquals(id.getValue(), RESULT2);
-        Assert.assertEquals(id.getFormat(), NameID.PERSISTENT);
-        Assert.assertEquals(id.getNameQualifier(), TestSources.IDP_ENTITY_ID);
-        Assert.assertEquals(id.getSPNameQualifier(), TestSources.SP_ENTITY_ID);
-    }
-
-    @Test
-    public void testBase32ComputedId() throws Exception {
-        final ComputedPersistentIdGenerationStrategy strategy = new ComputedPersistentIdGenerationStrategy();
-        strategy.setSalt(salt);
-        strategy.setEncoding(Encoding.BASE32);
-        strategy.initialize();
-
-        generator.setPersistentIdGenerator(strategy);
-        generator.setAttributeSourceIds(Collections.singletonList("SOURCE"));
-        generator.initialize();
-        
-        prc.getSubcontext(SubjectContext.class, true).setPrincipalName("foo");
-        Assert.assertNull(generator.generate(prc, NameID.PERSISTENT));
-        
-        final IdPAttribute source = new IdPAttribute("SOURCE");
-        source.setValues(Collections.singleton(new StringAttributeValue(TestSources.COMMON_ATTRIBUTE_VALUE_STRING)));
-        prc.getSubcontext(RelyingPartyContext.class).getSubcontext(AttributeContext.class, true).setUnfilteredIdPAttributes(
-                Collections.singleton(source));
-        final NameID id = generator.generate(prc, NameID.PERSISTENT);
-        Assert.assertNotNull(id);
-        Assert.assertEquals(id.getValue(), B32RESULT);
-        Assert.assertEquals(id.getFormat(), NameID.PERSISTENT);
-        Assert.assertEquals(id.getNameQualifier(), TestSources.IDP_ENTITY_ID);
-        Assert.assertEquals(id.getSPNameQualifier(), TestSources.SP_ENTITY_ID);
-    }
-
-    @Test
     public void testStoredId() throws Exception {
-        final StoredPersistentIdGenerationStrategy strategy = new StoredPersistentIdGenerationStrategy();
-        strategy.setDataSource(testSource);
-        strategy.initialize();
-
-        generator.setPersistentIdGenerator(strategy);
+        generator.setDataSource(testSource);
         
         testStoredIdLogic();
     }
 
     @Test
     public void testComputedAndStoredId() throws Exception {
-        final ComputedPersistentIdGenerationStrategy strategy = new ComputedPersistentIdGenerationStrategy();
+        final ComputedPairwiseIdStore strategy = new ComputedPairwiseIdStore();
         strategy.setSalt(salt);
         strategy.initialize();
 
-        final JDBCPersistentIdStoreEx store = new JDBCPersistentIdStoreEx();
+        final JDBCPairwiseIdStore store = new JDBCPairwiseIdStore();
         store.setDataSource(testSource);
+        store.setInitialValueStore(strategy);
         store.initialize();
         
-        final StoredPersistentIdGenerationStrategy strategy2 = new StoredPersistentIdGenerationStrategy();
-        strategy2.setIDStore(store);
-        strategy2.setComputedIdStrategy(strategy);
-        strategy2.initialize();
-        
-        generator.setPersistentIdGenerator(strategy2);
+        generator.setPersistentIdStore(store);
         
         testComputedAndStoredIdLogic();
 
-        store.deactivate(TestSources.IDP_ENTITY_ID, TestSources.SP_ENTITY_ID, RESULT, null);
+        final PairwiseId pid = new PairwiseId();
+        pid.setIssuerEntityID(TestSources.IDP_ENTITY_ID);
+        pid.setRecipientEntityID(TestSources.SP_ENTITY_ID);
+        pid.setPairwiseId(RESULT);
+        store.deactivate(pid);
         
         final NameID id = generator.generate(prc, NameID.PERSISTENT);
         Assert.assertNotEquals(id.getValue(), RESULT);
diff --git a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/nameid/impl/StoredPersistentIdDecoderTest.java b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/nameid/impl/StoredPersistentIdDecoderTest.java
index 6601f4c..2662dfe 100644
--- a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/nameid/impl/StoredPersistentIdDecoderTest.java
+++ b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/nameid/impl/StoredPersistentIdDecoderTest.java
@@ -26,6 +26,7 @@ import javax.sql.DataSource;
 import net.shibboleth.idp.attribute.IdPAttribute;
 import net.shibboleth.idp.attribute.StringAttributeValue;
 import net.shibboleth.idp.attribute.context.AttributeContext;
+import net.shibboleth.idp.attribute.impl.JDBCPairwiseIdStore;
 import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
 import net.shibboleth.idp.authn.context.SubjectContext;
 import net.shibboleth.idp.profile.RequestContextBuilder;
@@ -67,17 +68,13 @@ public class StoredPersistentIdDecoderTest extends OpenSAMLInitBaseTestCase {
 
     @BeforeMethod public void setUp() throws SQLException, IOException, ComponentInitializationException {
         
-        final JDBCPersistentIdStoreEx store = new JDBCPersistentIdStoreEx();
+        final JDBCPairwiseIdStore store = new JDBCPairwiseIdStore();
         store.setDataSource(testSource);
         store.initialize();
         
-        final StoredPersistentIdGenerationStrategy strategy = new StoredPersistentIdGenerationStrategy();
-        strategy.setIDStore(store);
-        strategy.initialize();
-        
         generator = new PersistentSAML2NameIDGenerator();
         generator.setId("test");
-        generator.setPersistentIdGenerator(strategy);
+        generator.setPersistentIdStore(store);
         generator.setAttributeSourceIds(Collections.singletonList("SOURCE"));
         
         decoder = new StoredPersistentIdDecoder();

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list