]> git.aero2k.de Git - tmp/jakarta-migration.git/commitdiff
CSPACE-307 CSPACE-579 CSPACE-581
authorSanjay Dalal <sanjay.dalal@berkeley.edu>
Fri, 27 Nov 2009 00:26:52 +0000 (00:26 +0000)
committerSanjay Dalal <sanjay.dalal@berkeley.edu>
Fri, 27 Nov 2009 00:26:52 +0000 (00:26 +0000)
integrated account management with default id provider management. create account would create a user in the default id provider and delete would delete if present. if password is provided on account create, a user would be created in the default id provider. added tenant id and userid in account. consolidated persistence.xml (jaxrsprovider/src/main/resources) so that DML could be performed atomically on all cs entities involved under the same transaction. added gen_ddl task to account(client)
test: account service tests and all service tests

14 files changed:
services/JaxRsServiceProvider/src/main/resources/META-INF/persistence.xml [moved from services/account/service/src/main/resources/META-INF/persistence.xml with 80% similarity]
services/account/client/build.xml
services/account/client/src/main/resources/db/mysql/account.sql
services/account/client/src/test/java/org/collectionspace/services/client/test/AccountServiceTest.java
services/account/client/src/test/java/org/collectionspace/services/client/test/AccountTest.java
services/account/client/src/test/resources/log4j.properties
services/account/jaxb/src/main/resources/accounts_common.xsd
services/account/service/src/main/java/org/collectionspace/services/account/storage/AccountStorageClient.java
services/authentication/jaxb/src/main/resources/META-INF/persistence.xml [deleted file]
services/authentication/jaxb/src/main/resources/authentication_identity_provider.xsd
services/authentication/service/src/main/resources/db/mysql/authentication.sql
services/common/pom.xml
services/common/src/main/java/org/collectionspace/services/common/security/SecurityUtils.java [new file with mode: 0644]
services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageClient.java

similarity index 80%
rename from services/account/service/src/main/resources/META-INF/persistence.xml
rename to services/JaxRsServiceProvider/src/main/resources/META-INF/persistence.xml
index 9abf6226f88cab64508d018cb8b42fd70096d00e..c8b051378bf992fb8371f4a85b951b8c08f75f71 100644 (file)
@@ -1,12 +1,15 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <persistence version="1.0" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd
 http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:orm="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
-    <persistence-unit name="org.collectionspace.services.account">
+    <persistence-unit name="org.collectionspace.services">
         <provider>org.hibernate.ejb.HibernatePersistence</provider>
         <non-jta-data-source>CspaceDS</non-jta-data-source>
         <class>org.collectionspace.services.account.AccountsCommon</class>
         <class>org.collectionspace.services.account.AccountsCommonList</class>
         <class>org.collectionspace.services.account.AccountsCommonList$AccountListItem</class>
+        <class>org.collectionspace.services.authentication.User</class>
+        <class>org.collectionspace.services.authentication.Role</class>
+        <class>org.collectionspace.services.authentication.UserRole</class>
         <properties>
             <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
             <property name="hibernate.max_fetch_depth" value="3"/>
index 1a316e46ea18ed31832a142fe4873fb77832cd2c..380c36fa9c8c76cabae7206e53d4b294767c17c7 100644 (file)
         </exec>\r
     </target>\r
 \r
+    <target name="gen_ddl" depends="gen_ddl-unix,gen_ddl-windows"\r
+  description="geneate ddl" />\r
+    <target name="gen_ddl-unix" if="osfamily-unix">\r
+        <exec executable="mvn" failonerror="true">\r
+            <arg value="-Pddl" />\r
+            <arg value="process-test-resources" />\r
+            <arg value="-f" />\r
+            <arg value="${basedir}/pom.xml" />\r
+            <arg value="-N" />\r
+            <arg value="${mvn.opts}" />\r
+        </exec>\r
+    </target>\r
+    <target name="gen_ddl-windows" if="osfamily-windows">\r
+        <exec executable="cmd" failonerror="true">\r
+            <arg value="/c" />\r
+            <arg value="mvn.bat" />\r
+            <arg value="-Pddl" />\r
+            <arg value="process-test-resources" />\r
+            <arg value="-f" />\r
+            <arg value="${basedir}/pom.xml" />\r
+            <arg value="-N" />\r
+            <arg value="${mvn.opts}" />\r
+        </exec>\r
+    </target>\r
+\r
     <target name="create_db"\r
     description="create tables(s), indices for account service">\r
         <sql driver="com.mysql.jdbc.Driver"\r
index dbecbbb4a82b86f945e12b8a47b9338c9252e524..4f436d8b9f0bebc7f41c3de104c117222844d63f 100644 (file)
@@ -1,2 +1,2 @@
 drop table if exists accounts_common;
-create table accounts_common (csid varchar(255) not null, email longtext not null, first_name longtext not null, last_name longtext not null, mi varchar(1), mobile varchar(15), phone varchar(15), screen_name varchar(128) not null, primary key (csid));
+create table accounts_common (csid varchar(255) not null, email longtext not null, first_name longtext not null, last_name longtext not null, mi varchar(1), mobile varchar(15), phone varchar(15), screen_name varchar(128) not null, tenantid varchar(255) not null, userid longtext not null, primary key (csid));
index 95ec4e34f26528c85a629c579f54d5e73bce9655..07950afb52bfce4de221c8b02f916c4378370ac2 100644 (file)
@@ -75,7 +75,7 @@ public class AccountServiceTest extends AbstractServiceTest {
 
         // Submit the request to the service and store the response.
         AccountsCommon account =
-                createAccountInstance("barney", "dino", "barney", "hello", "barney@dinoland.com");
+                createAccountInstance("barney", "dino", "barney", "hithere08", "barney@dinoland.com");
         ClientResponse<Response> res = client.create(account);
         int statusCode = res.getStatus();
 
@@ -100,18 +100,13 @@ public class AccountServiceTest extends AbstractServiceTest {
         }
     }
 
-    /* (non-Javadoc)
-     * @see org.collectionspace.services.client.test.ServiceTest#createList()
-     */
+    //to not cause uniqueness violation for account, createList is removed
     @Override
     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTest.class,
     dependsOnMethods = {"create"})
     public void createList(String testName) throws Exception {
-        for (int i = 0; i < 3; i++) {
-            create(testName);
-        }
     }
-
+    
     // Failure outcomes
     // Placeholders until the three tests below can be uncommented.
     // See Issue CSPACE-401.
@@ -413,7 +408,7 @@ public class AccountServiceTest extends AbstractServiceTest {
         account.setFirstName(firstName);
         account.setLastName(lastName);
         account.setScreenName(screenName);
-        account.setUserName(screenName);
+        account.setUserId(screenName);
         byte[] b64passwd = Base64.encodeBase64(passwd.getBytes());
         account.setPassword(b64passwd);
         account.setEmail(email);
index 2e3b06cc5937e7267a98b33b146f745fe75fb839..2a4f04e5332aa6f0a2754f4d95965f29407adb0b 100644 (file)
@@ -60,8 +60,10 @@ public class AccountTest {
         account.setFirstName("John");
         account.setLastName("Doe");
         account.setEmail("john.doe@berkeley.edu");
+        account.setUserId("johndoe");
         id = UUID.randomUUID().toString();
         account.setCsid(id);
+        account.setTenantid("123"); //set by service runtime
         em.getTransaction().begin();
         em.persist(account);
         // Commit the transaction
index 18c510350fd16aa0b59d63d387ee5634a4d3fea1..fa374e9247a5312a8291264f0aa0e2e1a201863b 100644 (file)
@@ -21,3 +21,4 @@ log4j.logger.org.collectionspace=DEBUG
 log4j.logger.org.apache=INFO\r
 log4j.logger.httpclient=INFO\r
 log4j.logger.org.jboss.resteasy=INFO\r
+log4j.logger.org.hibernate=INFO\r
index 4c8914f7c174ba862351d545977170b6b352eb9d..9384ea9fa8fd04f776da2c23bbc209f08d5da5fb 100644 (file)
                         </xs:appinfo>
                     </xs:annotation>
                 </xs:element>
-                <!-- optional username for default identity provider -->
-                <xs:element name="userName" type="xs:string" minOccurs="0" maxOccurs="1">
+                <!-- userid, could be calnet id, openid URI -->
+                <xs:element name="userId" type="xs:string" minOccurs="0" maxOccurs="1">
                     <xs:annotation>
                         <xs:appinfo>
-                            <hj:ignored/>
+                            <hj:basic>
+                                <orm:column name="userid" length="512"  nullable="false"/>
+                            </hj:basic>
                         </xs:appinfo>
                     </xs:annotation>
                 </xs:element>
-                <!-- optional base64 encoded password for default identity provider -->
+                <!-- optional base64 encoded password for default identity provider only -->
                 <xs:element name="password" type="xs:base64Binary" minOccurs="0" maxOccurs="1">
                     <xs:annotation>
                         <xs:appinfo>
                         </xs:appinfo>
                     </xs:annotation>
                 </xs:element>
+                <!-- tenant id is not needed from service consumer, it will be ignored if provided -->
+                <!-- tenant id is for internal use for CollectionSpace -->
+                <!-- it is assumed that account creation is performed under valid -->
+                <!-- tenant context. tenant id is never returned in response -->
+                <xs:element name="tenantid" type="xs:string" minOccurs="0" maxOccurs="1">
+                    <xs:annotation>
+                        <xs:appinfo>
+                            <hj:basic>
+                                <orm:column name="tenantid" nullable="false"/>
+                            </hj:basic>
+                        </xs:appinfo>
+                    </xs:annotation>
+                </xs:element>
             </xs:sequence>
             <xs:attribute name="csid" type="xs:string">
                 <xs:annotation>
index cfbe193718de5bd8756594e5fc70a37fe86fd661..8131f10ff8c84bfd366949f3c1974f5b8866f882 100644 (file)
  */
 package org.collectionspace.services.account.storage;
 
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Query;
+import org.apache.commons.codec.binary.Base64;
+import org.collectionspace.services.account.AccountsCommon;
+import org.collectionspace.services.account.AccountsCommon;
+import org.collectionspace.services.authentication.User;
+import org.collectionspace.services.common.context.ServiceContext;
+import org.collectionspace.services.common.document.BadRequestException;
+import org.collectionspace.services.common.document.DocumentException;
+import org.collectionspace.services.common.document.DocumentHandler;
+import org.collectionspace.services.common.document.DocumentHandler.Action;
+import org.collectionspace.services.common.document.DocumentNotFoundException;
+import org.collectionspace.services.common.document.DocumentWrapper;
+import org.collectionspace.services.common.document.DocumentWrapperImpl;
+import org.collectionspace.services.common.security.SecurityUtils;
 import org.collectionspace.services.common.storage.jpa.JpaStorageClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * AccountStorageClient deals with both Account and Default Identity Provider's
  * state in persistent storage
  * @author 
  */
-public class AccountStorageClient extends JpaStorageClient
-{
+public class AccountStorageClient extends JpaStorageClient {
 
+    private final Logger logger = LoggerFactory.getLogger(AccountStorageClient.class);
+
+    public AccountStorageClient() {
+    }
+
+    @Override
+    public String create(ServiceContext ctx,
+            DocumentHandler handler) throws BadRequestException,
+            DocumentException {
+
+        String docType = ctx.getDocumentType();
+        if (docType == null) {
+            throw new DocumentNotFoundException(
+                    "Unable to find DocumentType for service " + ctx.getServiceName());
+        }
+        if (handler == null) {
+            throw new IllegalArgumentException(
+                    "AccountStorageClient.create: handler is missing");
+        }
+        EntityManagerFactory emf = null;
+        EntityManager em = null;
+        try {
+            handler.prepare(Action.CREATE);
+            AccountsCommon account = (AccountsCommon) handler.getCommonPart();
+            DocumentWrapper<AccountsCommon> wrapDoc =
+                    new DocumentWrapperImpl<AccountsCommon>(account);
+            handler.handle(Action.CREATE, wrapDoc);
+            emf = getEntityManagerFactory();
+            em = emf.createEntityManager();
+            em.getTransaction().begin();
+            //if userid and password are given, add to default id provider
+            if (account.getUserId() != null && account.getPassword() != null) {
+                User user = createUser(account);
+                em.persist(user);
+            }
+            account.setTenantid(ctx.getTenantId());
+            em.persist(account);
+            em.getTransaction().commit();
+            handler.complete(Action.CREATE, wrapDoc);
+            return (String) getValue(account, "getCsid");
+        } catch (Exception e) {
+            if (em != null && em.getTransaction().isActive()) {
+                em.getTransaction().rollback();
+            }
+            if (logger.isDebugEnabled()) {
+                logger.debug("Caught exception ", e);
+            }
+            throw new DocumentException(e);
+        } finally {
+            if (em != null) {
+                releaseEntityManagerFactory(emf);
+            }
+        }
+    }
+
+    @Override
+    public void delete(ServiceContext ctx, String id)
+            throws DocumentNotFoundException,
+            DocumentException {
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("deleting entity with id=" + id);
+        }
+        String docType = ctx.getDocumentType();
+        if (docType == null) {
+            throw new DocumentNotFoundException(
+                    "Unable to find DocumentType for service " + ctx.getServiceName());
+        }
+        EntityManagerFactory emf = null;
+        EntityManager em = null;
+        try {
+            emf = getEntityManagerFactory();
+            em = emf.createEntityManager();
+            //TODO investigate if deep delete is possible
+            //query an delete is inefficient
+            AccountsCommon accountFound = em.find(AccountsCommon.class, id);
+            if (accountFound == null) {
+                if (em != null && em.getTransaction().isActive()) {
+                    em.getTransaction().rollback();
+                }
+                String msg = "could not find entity with id=" + id;
+                logger.error(msg);
+                throw new DocumentNotFoundException(msg);
+            }
+
+            StringBuilder accDelStr = new StringBuilder("DELETE FROM ");
+            accDelStr.append(getEntityName(ctx));
+            accDelStr.append(" WHERE csid = :csid");
+            //TODO: add tenant id
+            Query accDel = em.createQuery(accDelStr.toString());
+            accDel.setParameter("csid", id);
+            //TODO: add tenant id
+
+            //if userid gives any indication about the id provider, it should
+            //be used to avoid the following approach
+            User userLocal = em.find(User.class, accountFound.getUserId());
+            Query usrDel = null;
+            if (userLocal != null) {
+                StringBuilder usrDelStr = new StringBuilder("DELETE FROM ");
+                usrDelStr.append(User.class.getCanonicalName());
+                usrDelStr.append(" WHERE username = :username");
+                //TODO: add tenant id
+                usrDel = em.createQuery(usrDelStr.toString());
+                usrDel.setParameter("username", accountFound.getUserId());
+            }
+            em.getTransaction().begin();
+            int accDelCount = accDel.executeUpdate();
+            if (accDelCount != 1) {
+                if (em != null && em.getTransaction().isActive()) {
+                    em.getTransaction().rollback();
+                }
+            }
+            if (userLocal != null) {
+                int usrDelCount = usrDel.executeUpdate();
+                if (usrDelCount != 1) {
+                    if (em != null && em.getTransaction().isActive()) {
+                        em.getTransaction().rollback();
+                    }
+                }
+                if (usrDelCount != 1) {
+                    String msg = "could not find user with username=" + accountFound.getUserId();
+                    logger.error(msg);
+                    throw new DocumentNotFoundException(msg);
+                }
+            }
+            em.getTransaction().commit();
+
+        } catch (DocumentException de) {
+            throw de;
+        } catch (Exception e) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Caught exception ", e);
+            }
+            if (em != null && em.getTransaction().isActive()) {
+                em.getTransaction().rollback();
+            }
+            throw new DocumentException(e);
+        } finally {
+            if (emf != null) {
+                releaseEntityManagerFactory(emf);
+            }
+        }
+    }
+
+    private User createUser(AccountsCommon account) {
+        User user = new User();
+        user.setUsername(account.getUserId());
+        byte[] bpass = Base64.decodeBase64(account.getPassword());
+        SecurityUtils.validatePassword(new String(bpass));
+        String secEncPasswd = SecurityUtils.createPasswordHash(
+                account.getUserId(), new String(bpass));
+        user.setPasswd(secEncPasswd);
+        return user;
+    }
 }
diff --git a/services/authentication/jaxb/src/main/resources/META-INF/persistence.xml b/services/authentication/jaxb/src/main/resources/META-INF/persistence.xml
deleted file mode 100644 (file)
index 6a124e8..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:orm="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd">
-  <persistence-unit name="org.collectionspace.services.authentication">
-    <non-jta-data-source>java:comp/env/jdbc/CspaceDS</non-jta-data-source>
-    <class>org.collectionspace.services.authentication.User</class>
-    <class>org.collectionspace.services.authentication.Role</class>
-    <class>org.collectionspace.services.authentication.UserRole</class>
-    <properties>
-      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
-      <property name="hibernate.max_fetch_depth" value="3"/>
-      <!--property name="hibernate.hbm2ddl.auto" value="create-drop"/-->
-    </properties>
-  </persistence-unit>
-</persistence>
index 899c0675d0d10ea92ffa9271760196c38e000dab..a49a1e7553a80c33a44a6f2da70f4ed4fc769e73 100644 (file)
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 
 <!--
-    CollectionSpace identity provider schema (XSD)
+    CollectionSpace default (security) identity provider schema (XSD)
     
-    Entity  :
+    Entity(s)  : users, roles, users_roles
     Used for: 
     
     $LastChangedRevision: 916 $
@@ -41,7 +41,7 @@
                     <xs:annotation>
                         <xs:appinfo>
                             <hj:id>
-                                <orm:column name="username"  length="128" nullable="false"/>
+                                <orm:column name="username"  length="512" nullable="false"/>
                             </hj:id>
                         </xs:appinfo>
                     </xs:annotation>
index 97be14377233db44e2a0c0162343cce4f6de943f..aa853867a037faacca4e39c8dd6262ae993c8955 100644 (file)
@@ -6,7 +6,7 @@
 use cspace;\r
 \r
 DROP TABLE IF EXISTS users;\r
-CREATE TABLE users(username VARCHAR(128) PRIMARY KEY, passwd VARCHAR(128) NOT NULL );\r
+CREATE TABLE users(username VARCHAR(512) PRIMARY KEY, passwd VARCHAR(128) NOT NULL );\r
 CREATE INDEX username_users on users(username);\r
 \r
 DROP TABLE IF EXISTS roles;\r
@@ -14,5 +14,5 @@ CREATE TABLE roles(rolename VARCHAR(128) PRIMARY KEY, rolegroup VARCHAR(128));
 CREATE INDEX rolename_roles on roles(rolename);\r
 \r
 DROP TABLE IF EXISTS users_roles;\r
-CREATE TABLE users_roles(username VARCHAR(128) NOT NULL, rolename VARCHAR(128) NOT NULL);\r
+CREATE TABLE users_roles(username VARCHAR(512) NOT NULL, rolename VARCHAR(128) NOT NULL);\r
 CREATE INDEX username_users_roles on users_roles(username);
\ No newline at end of file
index 9cd0d834c4e14e17a15312338a6dae96644ac6cc..2b6c1657ce79045bc90e2999000dd1832f91f750 100644 (file)
             <artifactId>hibernate-entitymanager</artifactId>\r
         </dependency>\r
         <!-- jboss -->\r
+        <dependency>\r
+            <groupId>jboss</groupId>\r
+            <artifactId>jbosssx</artifactId>\r
+            <version>4.2.3.GA</version>\r
+            <scope>provided</scope>\r
+        </dependency>\r
         <dependency>\r
             <groupId>jboss</groupId>\r
             <artifactId>jboss-remoting</artifactId>\r
diff --git a/services/common/src/main/java/org/collectionspace/services/common/security/SecurityUtils.java b/services/common/src/main/java/org/collectionspace/services/common/security/SecurityUtils.java
new file mode 100644 (file)
index 0000000..c7e8d46
--- /dev/null
@@ -0,0 +1,69 @@
+/**
+ * This document is a part of the source code and related artifacts
+ * for CollectionSpace, an open source collections management system
+ * for museums and related institutions:
+ *
+ * http://www.collectionspace.org
+ * http://wiki.collectionspace.org
+ *
+ * Copyright © 2009 Regents of the University of California
+ *
+ * Licensed under the Educational Community License (ECL), Version 2.0.
+ * You may not use this file except in compliance with this License.
+ *
+ * You may obtain a copy of the ECL 2.0 License at
+ * https://source.collectionspace.org/collection-space/LICENSE.txt
+ *
+ * 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 org.collectionspace.services.common.security;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author 
+ */
+public class SecurityUtils {
+
+    private static final Logger logger = LoggerFactory.getLogger(SecurityUtils.class);
+
+    /**
+     * createPasswordHash creates password has using configured digest algorithm
+     * and encoding
+     * @param user
+     * @param password in cleartext
+     * @return hashed password
+     */
+    public static String createPasswordHash(String username, String password) {
+        //TODO: externalize digest algo and encoding
+        return org.jboss.security.Util.createPasswordHash("SHA-256", //digest algo
+                "base64", //encoding
+                null, //charset
+                username,
+                password);
+    }
+
+    /**
+     * validatePassword validates password per configured password policy
+     * @param password
+     */
+    public static void validatePassword(String password) {
+        //TODO: externalize password length
+        if (password == null) {
+            String msg = "Password missing ";
+            logger.error(msg);
+            throw new IllegalArgumentException(msg);
+        }
+        if (password.length() < 8 || password.length() > 24) {
+            String msg = "Password length should be >8 and <24";
+            logger.error(msg);
+            throw new IllegalArgumentException(msg);
+        }
+    }
+}
index 4a8ef9f5152e17dbd99cfed2da1ddfdbab7a1ba5..3bf24e5f4f1289e409e17d6c9a2e4e5f65b455ac 100644 (file)
@@ -17,6 +17,7 @@
  */
 package org.collectionspace.services.common.storage.jpa;
 
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.List;
 import javax.persistence.EntityManager;
@@ -48,7 +49,7 @@ import org.slf4j.LoggerFactory;
 public class JpaStorageClient implements StorageClient {
 
     private final Logger logger = LoggerFactory.getLogger(JpaStorageClient.class);
-
+    protected final static String CS_PERSISTENCE_UNIT = "org.collectionspace.services";
     public JpaStorageClient() {
     }
 
@@ -73,13 +74,15 @@ public class JpaStorageClient implements StorageClient {
             Object entity = handler.getCommonPart();
             DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entity);
             handler.handle(Action.CREATE, wrapDoc);
-            emf = getEntityManagerFactory(docType);
+            emf = getEntityManagerFactory();
             em = emf.createEntityManager();
             em.getTransaction().begin();
             em.persist(entity);
             em.getTransaction().commit();
             handler.complete(Action.CREATE, wrapDoc);
-            return getCsid(entity);
+            return (String) getValue(entity, "getCsid");
+        } catch (DocumentException de) {
+            throw de;
         } catch (Exception e) {
             if (em != null && em.getTransaction().isActive()) {
                 em.getTransaction().rollback();
@@ -126,7 +129,7 @@ public class JpaStorageClient implements StorageClient {
             if ((null != where) && (where.length() > 0)) {
                 queryStr.append(" AND " + where);
             }
-            emf = getEntityManagerFactory(docType);
+            emf = getEntityManagerFactory();
             em = emf.createEntityManager();
             Query q = em.createQuery(queryStr.toString());
             q.setParameter("csid", id);
@@ -154,8 +157,6 @@ public class JpaStorageClient implements StorageClient {
             DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(o);
             handler.handle(Action.GET, wrapDoc);
             handler.complete(Action.GET, wrapDoc);
-        } catch (IllegalArgumentException iae) {
-            throw iae;
         } catch (DocumentException de) {
             throw de;
         } catch (Exception e) {
@@ -176,14 +177,7 @@ public class JpaStorageClient implements StorageClient {
         throw new UnsupportedOperationException("use getFiltered instead");
     }
 
-    /**
-     * getFiltered get all documents for an entity service from the Document repository,
-     * given filter parameters specified by the handler.
-     * @param ctx service context under which this method is invoked
-     * @param handler should be used by the caller to provide and transform the document
-     * @throws DocumentNotFoundException if workspace not found
-     * @throws DocumentException
-     */
+    @Override
     public void getFiltered(ServiceContext ctx, DocumentHandler handler)
             throws DocumentNotFoundException, DocumentException {
         if (handler == null) {
@@ -214,7 +208,7 @@ public class JpaStorageClient implements StorageClient {
             if ((null != where) && (where.length() > 0)) {
                 queryStr.append(" AND " + where);
             }
-            emf = getEntityManagerFactory(docType);
+            emf = getEntityManagerFactory();
             em = emf.createEntityManager();
             Query q = em.createQuery(queryStr.toString());
             //TODO: add tenant id
@@ -264,11 +258,11 @@ public class JpaStorageClient implements StorageClient {
             setCsid(entity, id);
             DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entity);
             handler.handle(Action.UPDATE, wrapDoc);
-            emf = getEntityManagerFactory(docType);
+            emf = getEntityManagerFactory();
             em = emf.createEntityManager();
             em.getTransaction().begin();
             Object entityFound = em.find(entity.getClass(), id);
-            if(entityFound == null) {
+            if (entityFound == null) {
                 if (em != null && em.getTransaction().isActive()) {
                     em.getTransaction().rollback();
                 }
@@ -293,13 +287,6 @@ public class JpaStorageClient implements StorageClient {
         }
     }
 
-    /**
-     * delete a document from the Nuxeo repository
-     * @param ctx service context under which this method is invoked
-     * @param id
-     *            of the document
-     * @throws DocumentException
-     */
     @Override
     public void delete(ServiceContext ctx, String id)
             throws DocumentNotFoundException,
@@ -321,7 +308,7 @@ public class JpaStorageClient implements StorageClient {
             deleteStr.append(" WHERE csid = :csid");
             //TODO: add tenant id
 
-            emf = getEntityManagerFactory(docType);
+            emf = getEntityManagerFactory();
             em = emf.createEntityManager();
             Query q = em.createQuery(deleteStr.toString());
             q.setParameter("csid", id);
@@ -356,40 +343,87 @@ public class JpaStorageClient implements StorageClient {
         }
     }
 
-    private EntityManagerFactory getEntityManagerFactory(
+    protected EntityManagerFactory getEntityManagerFactory() {
+        return getEntityManagerFactory(CS_PERSISTENCE_UNIT);
+    }
+    protected EntityManagerFactory getEntityManagerFactory(
             String persistenceUnit) {
         return Persistence.createEntityManagerFactory(persistenceUnit);
 
     }
 
-    private void releaseEntityManagerFactory(EntityManagerFactory emf) {
+    protected void releaseEntityManagerFactory(EntityManagerFactory emf) {
         if (emf != null) {
             emf.close();
         }
 
     }
 
-    private String getCsid(Object o) throws Exception {
-        Class c = o.getClass();
-        Method m = c.getMethod("getCsid");
-        if (m == null) {
-            String msg = "Could not find csid in entity of class=" + o.getClass().getCanonicalName();
+    /**
+     * getValue gets invokes specified accessor method on given object. Assumption
+     * is that this is used for JavaBean pattern getXXX methods only.
+     * @param o object to return value from
+     * @param methodName of method to invoke
+     * @return value returned of invocation
+     * @throws NoSuchMethodException
+     * @throws IllegalAccessException
+     * @throws InvocationTargetException
+     */
+    protected Object getValue(Object o, String methodName)
+            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+        if (methodName == null) {
+            String msg = methodName + " cannot be null";
             logger.error(msg);
             throw new IllegalArgumentException(msg);
         }
+        Class c = o.getClass();
+        Method m = c.getMethod(methodName);
 
         Object r = m.invoke(o);
         if (logger.isDebugEnabled()) {
-            logger.debug("getCsid returned csid=" + r +
+            logger.debug("getValue returned value=" + r +
                     " for " + c.getName());
         }
+        return r;
+    }
 
-        return (String) r;
+    /**
+     * setValue mutates the given object by invoking specified method. Assumption
+     * is that this is used for JavaBean pattern setXXX methods only.
+     * @param o object to mutate
+     * @param methodName indicates method to invoke
+     * @param argType type of the only argument (assumed) to method
+     * @param argValue value of the only argument (assumed) to method
+     * @return
+     * @throws NoSuchMethodException
+     * @throws IllegalAccessException
+     * @throws InvocationTargetException
+     */
+    protected Object setValue(Object o, String methodName, Class argType, Object argValue)
+            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+        if (methodName == null) {
+            String msg = methodName + " cannot be null";
+            logger.error(msg);
+            throw new IllegalArgumentException(msg);
+        }
+        if (argType == null) {
+            String msg = "argType cannot be null";
+            logger.error(msg);
+            throw new IllegalArgumentException(msg);
+        }
+        Class c = o.getClass();
+        Method m = c.getMethod(methodName, argType);
+        Object r = m.invoke(o, argValue);
+        if (logger.isDebugEnabled()) {
+            logger.debug("completed invocation of " + methodName +
+                    " for " + c.getName());
+        }
+        return r;
     }
 
-    private void setCsid(Object o, String csid) throws Exception {
-        //verify csid 
-        String id = getCsid(o);
+    protected void setCsid(Object o, String csid) throws Exception {
+        //verify csid
+        String id = (String) getValue(o, "getCsid");
         if (id != null) {
             if (!id.equals(csid)) {
                 String msg = "Csids do not match!";
@@ -402,23 +436,10 @@ public class JpaStorageClient implements StorageClient {
 
         }
         //set csid
-        Class c = o.getClass();
-        Method m = c.getMethod("setCsid", java.lang.String.class);
-        if (m == null) {
-            String msg = "Could not find csid in entity of class=" + o.getClass().getCanonicalName();
-            logger.error(msg);
-            throw new IllegalArgumentException(msg);
-        }
-
-        Object r = m.invoke(o, csid);
-        if (logger.isDebugEnabled()) {
-            logger.debug("completed setCsid " +
-                    " for " + c.getName());
-        }
-
+        setValue(o, "setCsid", java.lang.String.class, csid);
     }
 
-    private String getEntityName(ServiceContext ctx) {
+    protected String getEntityName(ServiceContext ctx) {
         Object o = ctx.getProperty("entity-name");
         if (o == null) {
             throw new IllegalArgumentException("property entity-name missing in context " +