diff --git a/pom.xml b/pom.xml
index fa30ae55..3e218ee0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -78,6 +78,8 @@
UTF-8
true
3.0.3
+ 2.4.1
+
+
+
+ redback-common
+ org.apache.archiva.redback
+ 2.5-SNAPSHOT
+
+ 4.0.0
+
+ redback-common-jpa
+ jar
+ Redback :: JPA Common Package
+
+
+
+ org.apache.archiva.redback
+ redback-rbac-model
+
+
+ org.apache.archiva.redback
+ redback-users-api
+
+
+
+ org.apache.openjpa
+ openjpa
+ ${openjpa.version}
+
+
+ org.hsqldb
+ hsqldb
+ test
+
+
+
+
+
\ No newline at end of file
diff --git a/redback-common/redback-common-jpa/src/main/resources/META-INF/persistence-hsqldb.xml b/redback-common/redback-common-jpa/src/main/resources/META-INF/persistence-hsqldb.xml
new file mode 100644
index 00000000..a78444e0
--- /dev/null
+++ b/redback-common/redback-common-jpa/src/main/resources/META-INF/persistence-hsqldb.xml
@@ -0,0 +1,41 @@
+
+
+
+
+ org.apache.openjpa.persistence.PersistenceProviderImpl
+ java:comp/env/jdbc/redbackjpa
+ org.apache.archiva.redback.users.jpa.model.JpaUser
+ org.apache.archiva.redback.rbac.jpa.model.JpaOperation
+ org.apache.archiva.redback.rbac.jpa.model.JpaResource
+ org.apache.archiva.redback.rbac.jpa.model.JpaPermission
+ org.apache.archiva.redback.rbac.jpa.model.JpaRole
+ org.apache.archiva.redback.rbac.jpa.model.JpaUserAssignment
+
+
+
+
+
+
+
+
+
+
+
diff --git a/redback-common/redback-common-jpa/src/main/resources/META-INF/persistence.xml b/redback-common/redback-common-jpa/src/main/resources/META-INF/persistence.xml
new file mode 100644
index 00000000..46878e88
--- /dev/null
+++ b/redback-common/redback-common-jpa/src/main/resources/META-INF/persistence.xml
@@ -0,0 +1,36 @@
+
+
+
+
+ org.apache.openjpa.persistence.PersistenceProviderImpl
+ java:comp/env/jdbc/redbackjpa
+ org.apache.archiva.redback.users.jpa.model.JpaUser
+ org.apache.archiva.redback.rbac.jpa.model.JpaOperation
+ org.apache.archiva.redback.rbac.jpa.model.JpaResource
+ org.apache.archiva.redback.rbac.jpa.model.JpaPermission
+ org.apache.archiva.redback.rbac.jpa.model.JpaRole
+ org.apache.archiva.redback.rbac.jpa.model.JpaUserAssignment
+
+
+
+
+
+
diff --git a/redback-rbac/redback-rbac-providers/pom.xml b/redback-rbac/redback-rbac-providers/pom.xml
index 0edb4970..a94d451d 100644
--- a/redback-rbac/redback-rbac-providers/pom.xml
+++ b/redback-rbac/redback-rbac-providers/pom.xml
@@ -32,5 +32,6 @@
redback-rbac-memory
redback-rbac-cached
redback-rbac-ldap
+ redback-rbac-jpa
diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/pom.xml b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/pom.xml
new file mode 100644
index 00000000..77045ab9
--- /dev/null
+++ b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/pom.xml
@@ -0,0 +1,70 @@
+
+
+
+
+ redback-rbac-providers
+ org.apache.archiva.redback
+ 2.5-SNAPSHOT
+
+ 4.0.0
+
+ redback-rbac-jpa
+ jar
+ Redback :: RBAC Provider :: JPA
+
+
+
+ org.apache.archiva.redback
+ redback-rbac-model
+
+
+ org.springframework
+ spring-context-support
+
+
+ javax.annotation
+ jsr250-api
+
+
+ org.apache.archiva.redback
+ redback-rbac-tests
+ test
+
+
+ org.apache.archiva.redback
+ redback-common-jpa
+
+
+
+ org.apache.openjpa
+ openjpa
+ ${openjpa.version}
+
+
+ org.hsqldb
+ hsqldb
+ test
+
+
+
+
+
\ No newline at end of file
diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/JpaRbacManager.java b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/JpaRbacManager.java
new file mode 100644
index 00000000..ced9104b
--- /dev/null
+++ b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/JpaRbacManager.java
@@ -0,0 +1,452 @@
+package org.apache.archiva.redback.rbac.jpa;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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.
+ */
+
+import org.apache.archiva.redback.rbac.*;
+import org.apache.archiva.redback.rbac.jpa.model.*;
+import org.apache.openjpa.persistence.Type;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import javax.persistence.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Created by martin on 20.09.16.
+ */
+@Service("rbacManager#jpa")
+public class JpaRbacManager extends AbstractRBACManager {
+
+
+ @PersistenceContext(unitName = "redback-jpa")
+ EntityManager em;
+
+
+ private AtomicBoolean initialized = new AtomicBoolean(false);
+
+
+ public void setEntityManager(EntityManager em) {
+ this.em = em;
+ }
+
+
+
+ @Override
+ public Role createRole(String name) {
+ JpaRole role = new JpaRole();
+ role.setName(name);
+ return role;
+ }
+
+ @Override
+ public Role saveRole(Role role) throws RbacObjectInvalidException, RbacManagerException {
+ RBACObjectAssertions.assertValid( role );
+ final EntityManager em = getEm();
+ em.getTransaction().begin();
+ em.persist(role);
+
+ em.getTransaction().commit();
+ fireRbacRoleSaved(role);
+ for (Permission perm : role.getPermissions()) {
+ fireRbacPermissionSaved(perm);
+ }
+ return role;
+ }
+
+ @Override
+ public void saveRoles(Collection roles) throws RbacObjectInvalidException, RbacManagerException {
+ if ( roles == null )
+ {
+ // Nothing to do.
+ return;
+ }
+
+ final EntityManager em = getEm();
+ em.getTransaction().begin();
+ for (Role role : roles ) {
+ RBACObjectAssertions.assertValid(role);
+ em.persist(role);
+ }
+ em.getTransaction().commit();
+ for (Role role : roles) {
+ fireRbacRoleSaved(role);
+ }
+ }
+
+ @Override
+ public Role getRole(String roleName) throws RbacObjectNotFoundException, RbacManagerException {
+ final EntityManager em = getEm();
+ TypedQuery q = em.createQuery("SELECT r FROM JpaRole r WHERE r.name = :rolename", JpaRole.class);
+ q.setParameter("rolename",roleName);
+ return q.getSingleResult();
+ }
+
+ @Override
+ public List getAllRoles() throws RbacManagerException {
+ final EntityManager em = getEm();
+ Query q = em.createQuery("SELECT r FROM JpaRole r");
+ return q.getResultList();
+ }
+
+ @Override
+ public void removeRole(Role role) throws RbacObjectNotFoundException, RbacObjectInvalidException, RbacManagerException {
+ RBACObjectAssertions.assertValid(role);
+ if (!(role instanceof JpaRole)) {
+ throw new RbacObjectInvalidException("Role object is not instance of JpaRole");
+ }
+ if ( role.isPermanent() )
+ {
+ throw new RbacPermanentException( "Unable to delete permanent role [" + role.getName() + "]" );
+ }
+ final EntityManager em = getEm();
+ em.getTransaction().begin();
+ JpaRole myRole = em.find(JpaRole.class, role.getName());
+ if (myRole == null) {
+ throw new RbacObjectNotFoundException("Role not found "+role.getName());
+ }
+ myRole.setPermissions(new ArrayList());
+ em.remove(myRole);
+ em.getTransaction().commit();
+ fireRbacRoleRemoved(myRole);
+ }
+
+ @Override
+ public Permission createPermission(String name) throws RbacManagerException {
+ JpaPermission permission = new JpaPermission();
+ permission.setName(name);
+ return permission;
+ }
+
+ @Override
+ public Permission createPermission(String name, String operationName, String resourceIdentifier) throws RbacManagerException {
+ JpaPermission permission = new JpaPermission();
+ permission.setName(name);
+ Operation op;
+ try {
+ op = getOperation(operationName);
+ } catch (RbacObjectNotFoundException ex) {
+ op = createOperation(operationName);
+ }
+ permission.setOperation(op);
+ Resource res;
+ try {
+ res = getResource(resourceIdentifier);
+ } catch (RbacObjectNotFoundException ex) {
+ res = createResource(resourceIdentifier);
+ }
+ permission.setResource(res);
+ return permission;
+ }
+
+ @Override
+ public Permission savePermission(Permission permission) throws RbacObjectInvalidException, RbacManagerException {
+ RBACObjectAssertions.assertValid(permission);
+ if (!(permission instanceof JpaPermission)) {
+ throw new RbacObjectInvalidException("The permission object ist not instance of JpaPermission");
+ }
+ final EntityManager em = getEm();
+ em.getTransaction().begin();
+ em.persist(permission);
+ em.getTransaction().commit();
+ fireRbacPermissionSaved(permission);
+ return permission;
+ }
+
+ @Override
+ public Permission getPermission(String permissionName) throws RbacObjectNotFoundException, RbacManagerException {
+ final EntityManager em = getEm();
+ TypedQuery q = em.createQuery("SELECT p FROM JpaPermission p WHERE p.name=:name", Permission.class);
+ q.setParameter("name",permissionName);
+ Permission res = q.getSingleResult();
+ if (res==null) {
+ throw new RbacObjectNotFoundException("Permission "+permissionName+" not found");
+ }
+ return res;
+ }
+
+ @Override
+ public List getAllPermissions() throws RbacManagerException {
+ final EntityManager em = getEm();
+ TypedQuery q = em.createQuery("SELECT p FROM JpaPermission p",JpaPermission.class);
+ return (List)(List>)q.getResultList();
+ }
+
+ @Override
+ public void removePermission(Permission permission) throws RbacObjectNotFoundException, RbacObjectInvalidException, RbacManagerException {
+ RBACObjectAssertions.assertValid(permission);
+ if (!(permission instanceof JpaPermission)) {
+ throw new RbacObjectInvalidException("The permission object is not JpaPermission object");
+ }
+ if ( permission.isPermanent() )
+ {
+ throw new RbacPermanentException( "Unable to delete permanent permission [" + permission.getName() + "]" );
+ }
+ final EntityManager em = getEm();
+ em.getTransaction().begin();
+ Permission p = em.find(JpaPermission.class, permission.getName());
+ if (p == null) {
+ throw new RbacObjectNotFoundException("Permission " + permission.getName() + " not found");
+ }
+ em.remove(p);
+ em.getTransaction().commit();
+ fireRbacPermissionRemoved(p);
+ }
+
+ @Override
+ public Operation createOperation(String name) throws RbacManagerException {
+ JpaOperation op = new JpaOperation();
+ op.setName(name);
+ return op;
+ }
+
+ @Override
+ public Operation saveOperation(Operation operation) throws RbacObjectInvalidException, RbacManagerException {
+ RBACObjectAssertions.assertValid(operation);
+ if (!(operation instanceof JpaOperation)) {
+ throw new RbacObjectInvalidException("Operation is not JpaOperation object");
+ }
+ final EntityManager em = getEm();
+ em.getTransaction().begin();
+ em.persist(operation);
+ em.getTransaction().commit();
+ return operation;
+ }
+
+ @Override
+ public Operation getOperation(String operationName) throws RbacObjectNotFoundException, RbacManagerException {
+ final EntityManager em = getEm();
+ Operation op = em.find(JpaOperation.class,operationName);
+ if(op==null) {
+ throw new RbacObjectNotFoundException("Operation "+operationName+" not found");
+ }
+ return op;
+ }
+
+ @Override
+ public List getAllOperations() throws RbacManagerException {
+ final EntityManager em = getEm();
+ Query q = em.createQuery("SELECT o FROM JpaOperation o");
+ return q.getResultList();
+ }
+
+ @Override
+ public void removeOperation(Operation operation) throws RbacObjectNotFoundException, RbacObjectInvalidException, RbacManagerException {
+ RBACObjectAssertions.assertValid(operation);
+ if (!(operation instanceof JpaOperation)) {
+ throw new RbacObjectInvalidException("Operation is not JpaOperation object");
+ }
+ if ( operation.isPermanent() )
+ {
+ throw new RbacPermanentException( "Unable to delete permanent operation [" + operation.getName() + "]" );
+ }
+ final EntityManager em = getEm();
+ em.getTransaction().begin();
+ Operation op = em.find(JpaOperation.class, operation.getName());
+ if (op==null) {
+ throw new RbacObjectNotFoundException("Operation not found "+operation.getName());
+ }
+ em.remove(op);
+ em.getTransaction().commit();
+
+ }
+
+ @Override
+ public Resource createResource(String identifier) throws RbacManagerException {
+ JpaResource resource = new JpaResource();
+ resource.setIdentifier(identifier);
+ return resource;
+ }
+
+ @Override
+ public Resource saveResource(Resource resource) throws RbacObjectInvalidException, RbacManagerException {
+ RBACObjectAssertions.assertValid(resource);
+ if (!(resource instanceof JpaResource)) {
+ throw new RbacObjectInvalidException("Resource is not JpaResource");
+ }
+ final EntityManager em = getEm();
+ em.getTransaction().begin();
+ em.persist(resource);
+ em.getTransaction().commit();
+ return resource;
+ }
+
+ @Override
+ public Resource getResource(String resourceIdentifier) throws RbacObjectNotFoundException, RbacManagerException {
+ final EntityManager em = getEm();
+ Resource r = em.find(JpaResource.class,resourceIdentifier);
+ if (r==null) {
+ throw new RbacObjectNotFoundException("Resource "+resourceIdentifier+" not found");
+ }
+ return r;
+ }
+
+ @Override
+ public List getAllResources() throws RbacManagerException {
+ final EntityManager em = getEm();
+ TypedQuery q = em.createQuery("SELECT r FROM JpaResource r",JpaResource.class);
+ return (List)(List>)q.getResultList();
+ }
+
+ @Override
+ public void removeResource(Resource resource) throws RbacObjectNotFoundException, RbacObjectInvalidException, RbacManagerException {
+ RBACObjectAssertions.assertValid(resource);
+ if (!(resource instanceof JpaResource)) {
+ throw new RbacObjectInvalidException("Resource is not JpaResource");
+ }
+ if (resource.isPermanent()) {
+ throw new RbacObjectInvalidException("Unable to delete permanent resource ["+resource.getIdentifier()+ "]");
+ }
+ final EntityManager em = getEm();
+ em.getTransaction().begin();
+ Resource res = em.find(JpaResource.class, resource.getIdentifier());
+ if (res==null) {
+ throw new RbacObjectNotFoundException("Resource "+resource.getIdentifier()+" not found");
+ }
+ em.remove(res);
+ em.getTransaction().commit();
+ }
+
+ @Override
+ public UserAssignment createUserAssignment(String principal) throws RbacManagerException {
+ JpaUserAssignment ua = new JpaUserAssignment();
+ ua.setPrincipal(principal);
+ return ua;
+ }
+
+ @Override
+ public UserAssignment saveUserAssignment(UserAssignment userAssignment) throws RbacObjectInvalidException, RbacManagerException {
+ RBACObjectAssertions.assertValid(userAssignment);
+ if (!(userAssignment instanceof JpaUserAssignment)) {
+ throw new RbacObjectInvalidException("Cannto save object that is not JpaUserAssignment");
+ }
+ final EntityManager em = getEm();
+ em.getTransaction().begin();
+ em.persist(userAssignment);
+ em.getTransaction().commit();
+ fireRbacUserAssignmentSaved(userAssignment);
+ return userAssignment;
+ }
+
+ @Override
+ public UserAssignment getUserAssignment(String principal) throws RbacObjectNotFoundException, RbacManagerException {
+ final EntityManager em = getEm();
+ UserAssignment ua = em.find(JpaUserAssignment.class, principal);
+ if (ua==null) {
+ throw new RbacObjectNotFoundException("User assignment not found "+principal);
+ }
+ return ua;
+ }
+
+ @Override
+ public List getAllUserAssignments() throws RbacManagerException {
+ final EntityManager em = getEm();
+ Query q = em.createQuery("SELECT ua FROM JpaUserAssignment ua");
+ return q.getResultList();
+ }
+
+ @Override
+ public List getUserAssignmentsForRoles(Collection roleNames) throws RbacManagerException {
+ final EntityManager em = getEm();
+ Query q = em.createQuery("SELECT ua FROM JpaUserAssignment ua, ua.roleNames rn WHERE rn IN :rolenames");
+ q.setParameter("rolenames",roleNames);
+ return q.getResultList();
+ }
+
+ @Override
+ public void removeUserAssignment(UserAssignment userAssignment) throws RbacObjectNotFoundException, RbacObjectInvalidException, RbacManagerException {
+ RBACObjectAssertions.assertValid(userAssignment);
+ if (userAssignment.isPermanent()) {
+ throw new RbacObjectInvalidException("Cannot remove permanent object "+userAssignment.getPrincipal());
+ }
+ final EntityManager em = getEm();
+ em.getTransaction().begin();
+ UserAssignment ua = em.find(UserAssignment.class, userAssignment.getPrincipal());
+ if (ua==null) {
+ throw new RbacObjectNotFoundException("User assignment not found "+userAssignment.getPrincipal());
+ }
+ em.remove(ua);
+ em.getTransaction().commit();
+ fireRbacUserAssignmentRemoved(userAssignment);
+ }
+
+ @Override
+ public void eraseDatabase() {
+ final EntityManager em = getEm();
+ // Deletion is a bit tricky, because the JPA bulk delete queries do not cascade
+ // or keep foreign keys into account.
+ em.getTransaction().begin();
+ TypedQuery tqp = em.createQuery("SELECT r FROM JpaPermission r",JpaPermission.class);
+ for(JpaPermission p : tqp.getResultList()) {
+ p.setOperation(null);
+ p.setResource(null);
+ }
+ TypedQuery tqr = em.createQuery("SELECT r FROM JpaRole r",JpaRole.class);
+ for (JpaRole r : tqr.getResultList()) {
+ r.getPermissions().clear();
+ }
+ em.flush();
+ TypedQuery tqo = em.createQuery("SELECT o FROM JpaOperation o",JpaOperation.class);
+ for(JpaOperation o : tqo.getResultList()) {
+ em.remove(o);
+ }
+ TypedQuery tqre = em.createQuery("SELECT re FROM JpaResource re",JpaResource.class);
+ for(JpaResource re : tqre.getResultList()) {
+ em.remove(re);
+ }
+ for (JpaPermission p : tqp.getResultList()) {
+ em.remove(p);
+ }
+ for (JpaRole r : tqr.getResultList()) {
+ em.remove(r);
+ }
+ TypedQuery tqu = em.createQuery("SELECT ua FROM JpaUserAssignment ua", JpaUserAssignment.class);
+ for(JpaUserAssignment ua : tqu.getResultList()) {
+ em.remove(ua);
+ }
+ em.getTransaction().commit();
+
+
+ }
+
+ @Override
+ public String getDescriptionKey() {
+ return "archiva.redback.rbacmanager.jpa";
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ private EntityManager getEm() {
+ if (initialized.compareAndSet(false, true)) {
+ Query q = em.createQuery("SELECT COUNT(r.name) FROM JpaRole r");
+ boolean dbInit = q.getFirstResult()==0;
+ fireRbacInit(dbInit);
+ }
+ return em;
+ }
+
+
+}
diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaOperation.java b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaOperation.java
new file mode 100644
index 00000000..66a41a15
--- /dev/null
+++ b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaOperation.java
@@ -0,0 +1,91 @@
+package org.apache.archiva.redback.rbac.jpa.model;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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.
+ */
+
+import org.apache.archiva.redback.rbac.Operation;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * Created by martin on 25.09.16.
+ */
+@Entity
+@Table(name="SECURITY_OPERATIONS")
+public class JpaOperation implements Operation, Serializable {
+
+ @Id
+ @Column(name="NAME")
+ private String name;
+ @Column(name="DESCRIPTION")
+ private String description;
+ @Column(name="PERMANENT")
+ private boolean permanent;
+
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public boolean isPermanent() {
+ return permanent;
+ }
+
+ @Override
+ public void setPermanent(boolean permanent) {
+ this.permanent = permanent;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ JpaOperation that = (JpaOperation) o;
+
+ return name.equals(that.name);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+}
diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaPermission.java b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaPermission.java
new file mode 100644
index 00000000..4ac69896
--- /dev/null
+++ b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaPermission.java
@@ -0,0 +1,123 @@
+package org.apache.archiva.redback.rbac.jpa.model;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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.
+ */
+
+import org.apache.archiva.redback.rbac.Operation;
+import org.apache.archiva.redback.rbac.Permission;
+import org.apache.archiva.redback.rbac.Resource;
+import org.apache.archiva.redback.rbac.jpa.JpaRbacManager;
+
+import javax.persistence.*;
+import java.io.Serializable;
+
+/**
+ * Created by martin on 25.09.16.
+ */
+@Entity
+@Table(name="SECURITY_PERMISSIONS")
+public class JpaPermission implements Permission,Serializable {
+
+ @Id
+ @Column(name="NAME")
+ private String name;
+ @Column(name="DESCRIPTION")
+ private String description;
+ @Column(name="PERMANENT")
+ private boolean permanent;
+ @ManyToOne(cascade = CascadeType.PERSIST)
+ @JoinColumn(
+ name="OPERATION_NAME_OID",
+ referencedColumnName = "NAME"
+ )
+ private JpaOperation operation;
+ @ManyToOne(cascade = CascadeType.PERSIST)
+ @JoinColumn(
+ name="RESOURCE_IDENTIFIER_OID",
+ referencedColumnName = "IDENTIFIER"
+ )
+ private JpaResource resource;
+
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public boolean isPermanent() {
+ return permanent;
+ }
+
+ @Override
+ public void setPermanent(boolean permanent) {
+ this.permanent = permanent;
+ }
+
+ @Override
+ public Operation getOperation() {
+ return operation;
+ }
+
+ @Override
+ public void setOperation(Operation operation) {
+ this.operation = (JpaOperation)operation;
+ }
+
+ @Override
+ public Resource getResource() {
+ return resource;
+ }
+
+ @Override
+ public void setResource(Resource resource) {
+ this.resource = (JpaResource)resource;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ JpaPermission that = (JpaPermission) o;
+
+ return name.equals(that.name);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+}
diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaResource.java b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaResource.java
new file mode 100644
index 00000000..fc7ee777
--- /dev/null
+++ b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaResource.java
@@ -0,0 +1,91 @@
+package org.apache.archiva.redback.rbac.jpa.model;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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.
+ */
+
+import org.apache.archiva.redback.rbac.Resource;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * Created by martin on 25.09.16.
+ */
+@Entity
+@Table(name="SECURITY_RESOURCES")
+public class JpaResource implements Resource, Serializable {
+
+ @Id
+ @Column(name="IDENTIFIER")
+ private String identifier;
+ @Column(name="PATTERN")
+ private boolean pattern;
+ @Column(name="PERMANENT")
+ private boolean permanent;
+
+
+ @Override
+ public String getIdentifier() {
+ return identifier;
+ }
+
+ @Override
+ public void setIdentifier(String identifier) {
+ this.identifier = identifier;
+ }
+
+ @Override
+ public boolean isPattern() {
+ return pattern;
+ }
+
+ @Override
+ public void setPattern(boolean pattern) {
+ this.pattern = pattern;
+ }
+
+ @Override
+ public boolean isPermanent() {
+ return permanent;
+ }
+
+ @Override
+ public void setPermanent(boolean permanent) {
+ this.permanent = permanent;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ JpaResource that = (JpaResource) o;
+
+ return identifier.equals(that.identifier);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return identifier.hashCode();
+ }
+}
diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaRole.java b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaRole.java
new file mode 100644
index 00000000..1f8a62c6
--- /dev/null
+++ b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaRole.java
@@ -0,0 +1,172 @@
+package org.apache.archiva.redback.rbac.jpa.model;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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.
+ */
+
+import org.apache.archiva.redback.rbac.AbstractRole;
+import org.apache.archiva.redback.rbac.Permission;
+
+import javax.persistence.*;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by martin on 25.09.16.
+ */
+@Entity
+@Table(
+ name="SECURITY_ROLES"
+)
+public class JpaRole extends AbstractRole implements Serializable {
+
+ @Id
+ @Column(name="NAME")
+ private String name;
+ @Column(name="DESCRIPTION")
+ private String description;
+ @Column(name="ASSIGNABLE")
+ private boolean assignable;
+ @Column(name="PERMANENT")
+ private boolean permanent;
+ @ManyToMany(cascade = CascadeType.PERSIST)
+ @JoinTable(
+ name="SECURITY_ROLE_PERMISSION_MAP",
+ joinColumns={ @JoinColumn(name="NAME_OID", referencedColumnName="NAME") },
+ inverseJoinColumns = {
+ @JoinColumn(name="NAME_EID",referencedColumnName = "NAME")
+ }
+ )
+ List permissions = new ArrayList();
+
+ @ElementCollection
+ @CollectionTable(
+ name="SECURITY_ROLE_CHILDROLE_MAP",
+ joinColumns = {
+ @JoinColumn(name="NAME_OID",referencedColumnName = "NAME")
+ }
+ )
+ List childRoleNames = new ArrayList();
+
+
+
+ @Override
+ public void addPermission(Permission permission) {
+ if (permission instanceof JpaPermission) {
+ this.permissions.add((JpaPermission) permission);
+ }
+
+ }
+
+ @Override
+ public void addChildRoleName(String name) {
+ this.childRoleNames.add(name);
+ }
+
+ @Override
+ public List getChildRoleNames() {
+ return childRoleNames;
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public List getPermissions() {
+ // Maybe better to create a new list?
+ return (List)(List>)permissions;
+ }
+
+ @Override
+ public boolean isAssignable() {
+ return assignable;
+ }
+
+ @Override
+ public void removePermission(Permission permission) {
+ this.permissions.remove(permission);
+ }
+
+ @Override
+ public void setAssignable(boolean assignable) {
+ this.assignable=assignable;
+ }
+
+ @Override
+ public void setChildRoleNames(List names) {
+ this.childRoleNames.clear();
+ this.childRoleNames.addAll(names);
+ }
+
+ @Override
+ public void setDescription(String description) {
+ this.description=description;
+
+ }
+
+ @Override
+ public void setName(String name) {
+ this.name=name;
+
+ }
+
+ @Override
+ public void setPermissions(List permissions) {
+ this.permissions.clear();
+ for (Permission p : permissions) {
+ if (p instanceof JpaPermission) {
+ permissions.add(p);
+ }
+ }
+ }
+
+ @Override
+ public boolean isPermanent() {
+ return permanent;
+ }
+
+ @Override
+ public void setPermanent(boolean permanent) {
+ this.permanent=permanent;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ JpaRole jpaRole = (JpaRole) o;
+
+ return name.equals(jpaRole.name);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+}
diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaUserAssignment.java b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaUserAssignment.java
new file mode 100644
index 00000000..39e0b2d3
--- /dev/null
+++ b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaUserAssignment.java
@@ -0,0 +1,97 @@
+package org.apache.archiva.redback.rbac.jpa.model;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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.
+ */
+
+import org.apache.archiva.redback.rbac.AbstractUserAssignment;
+
+import javax.persistence.*;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by Martin Stockhammer on 26.09.16.
+ */
+@Entity
+@Table(name="SECURITY_USER_ASSIGNMENTS")
+public class JpaUserAssignment extends AbstractUserAssignment implements Serializable {
+
+
+ @Id
+ @Column(name="PRINCIPAL")
+ private String principal;
+ @ElementCollection
+ @Column(name="STRING_ELE")
+ @CollectionTable(
+ name="SECURITY_USERASSIGNMENT_MAP",
+ joinColumns = {
+ @JoinColumn(name = "PRINCIPAL_OID", referencedColumnName = "PRINCIPAL")
+ }
+ )
+ private List roleNames = new ArrayList();
+ @Column(name="PERMANENT")
+ private boolean permanent = false;
+
+ @Override
+ public String getPrincipal() {
+ return principal;
+ }
+
+ @Override
+ public void setPrincipal(String principal) {
+ this.principal = principal;
+ }
+
+ @Override
+ public List getRoleNames() {
+ return roleNames;
+ }
+
+ @Override
+ public void setRoleNames(List roleNames) {
+ this.roleNames = roleNames;
+ }
+
+ @Override
+ public boolean isPermanent() {
+ return permanent;
+ }
+
+ @Override
+ public void setPermanent(boolean permanent) {
+ this.permanent = permanent;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ JpaUserAssignment that = (JpaUserAssignment) o;
+
+ return principal.equals(that.principal);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return principal.hashCode();
+ }
+}
diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/test/java/org/apache/archiva/redback/rbac/jpa/JpaRbacManagerTest.java b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/test/java/org/apache/archiva/redback/rbac/jpa/JpaRbacManagerTest.java
new file mode 100644
index 00000000..3c5bc9fa
--- /dev/null
+++ b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/test/java/org/apache/archiva/redback/rbac/jpa/JpaRbacManagerTest.java
@@ -0,0 +1,132 @@
+package org.apache.archiva.redback.rbac.jpa;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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.
+ */
+
+import org.apache.archiva.redback.common.jdo.test.StoreManagerDebug;
+import org.apache.archiva.redback.rbac.RbacManagerException;
+import org.apache.archiva.redback.tests.AbstractRbacManagerTestCase;
+import org.junit.Before;
+import org.springframework.test.annotation.DirtiesContext;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+import java.io.InputStream;
+import java.util.Properties;
+
+/**
+ * JdoRbacManagerTest:
+ *
+ * @author Jesse McConnell
+ * @author Joakim Erdfelt
+ */
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+public class JpaRbacManagerTest
+ extends AbstractRbacManagerTestCase
+{
+
+ @Inject
+ @Named(value = "rbacManager#jpa")
+ JpaRbacManager rbacManager;
+
+ public static int EVENTCOUNT = 2;
+
+ @Override
+ public void assertEventCount()
+ {
+ assertEquals( EVENTCOUNT, eventTracker.initCount );
+ }
+
+ /**
+ * Creates a new RbacStore which contains no data.
+ */
+ @Before
+ public void setUp()
+ throws Exception
+ {
+
+ super.setUp();
+ Properties props = new Properties();
+ InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("test.properties");
+ assert is!=null;
+ props.load(is);
+ is.close();
+ EntityManagerFactory emf = Persistence.createEntityManagerFactory("redback-jpa",props);
+
+ log.info("test setup");
+ rbacManager.setEntityManager(emf.createEntityManager());
+ super.setRbacManager(rbacManager);
+ assertNotNull(rbacManager);
+ log.info("injected rbac manager "+rbacManager);
+
+ }
+
+
+ @Override
+ public void testGetAssignedRoles()
+ throws RbacManagerException
+ {
+ super.testGetAssignedRoles();
+ }
+
+ @Override
+ public void testGetAssignedPermissionsDeep()
+ throws RbacManagerException
+ {
+ super.testGetAssignedPermissionsDeep();
+ }
+
+ @Override
+ protected void afterSetup()
+ {
+ super.afterSetup();
+ }
+
+ @Override
+ public void testLargeApplicationInit()
+ throws RbacManagerException
+ {
+ this.clearCache();
+ super.testLargeApplicationInit();
+ }
+
+ @Override
+ public void testGetRolesDeep()
+ throws RbacManagerException
+ {
+ this.clearCache();
+ super.testGetRolesDeep();
+ }
+
+
+ @Override
+ public void testStoreInitialization()
+ throws Exception
+ {
+ this.clearCache();
+ rbacManager.eraseDatabase();
+ eventTracker.rbacInit( true );
+ super.testStoreInitialization();
+ assertEquals( EVENTCOUNT, eventTracker.initCount );
+ }
+
+
+}
diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/test/resources/spring-context.xml b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/test/resources/spring-context.xml
new file mode 100644
index 00000000..abee0b5a
--- /dev/null
+++ b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/test/resources/spring-context.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/test/resources/test.properties b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/test/resources/test.properties
new file mode 100644
index 00000000..df848c45
--- /dev/null
+++ b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/test/resources/test.properties
@@ -0,0 +1,22 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF 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.
+openjpa.ConnectionURL=jdbc:hsqldb:mem:reback-jpa
+openjpa.ConnectionDriverName=org.hsqldb.jdbcDriver
+openjpa.ConnectionUserName=sa
+openjpa.ConnectionPassword=
+openjp.Log=DefaultLevel=WARN,Tool=INFO
+openjpa.jdbc.SynchronizeMappings=buildSchema(ForeignKeys=true)
\ No newline at end of file
diff --git a/redback-users/redback-users-providers/pom.xml b/redback-users/redback-users-providers/pom.xml
index 9ddab02c..4a906482 100644
--- a/redback-users/redback-users-providers/pom.xml
+++ b/redback-users/redback-users-providers/pom.xml
@@ -39,5 +39,6 @@
redback-users-jdo
redback-users-ldap
redback-users-configurable
+ redback-users-jpa
diff --git a/redback-users/redback-users-providers/redback-users-jpa/pom.xml b/redback-users/redback-users-providers/redback-users-jpa/pom.xml
new file mode 100644
index 00000000..2606b712
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-jpa/pom.xml
@@ -0,0 +1,68 @@
+
+
+
+
+ redback-users-providers
+ org.apache.archiva.redback
+ 2.5-SNAPSHOT
+
+ 4.0.0
+
+ redback-users-jpa
+ jar
+ Redback :: Users Provider :: JPA
+
+
+
+ org.springframework
+ spring-context-support
+
+
+ javax.annotation
+ jsr250-api
+
+
+ org.apache.archiva.redback
+ redback-policy
+
+
+ org.apache.archiva.redback
+ redback-common-jpa
+
+
+ org.apache.openjpa
+ openjpa
+ ${openjpa.version}
+
+
+
+ org.hsqldb
+ hsqldb
+ test
+
+
+ org.apache.archiva.redback
+ redback-users-tests
+ test
+
+
+
\ No newline at end of file
diff --git a/redback-users/redback-users-providers/redback-users-jpa/src/main/java/org/apache/archiva/redback/users/jpa/JpaUserManager.java b/redback-users/redback-users-providers/redback-users-jpa/src/main/java/org/apache/archiva/redback/users/jpa/JpaUserManager.java
new file mode 100644
index 00000000..c4c83f60
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-jpa/src/main/java/org/apache/archiva/redback/users/jpa/JpaUserManager.java
@@ -0,0 +1,307 @@
+package org.apache.archiva.redback.users.jpa;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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.
+ */
+
+import org.apache.archiva.redback.policy.UserSecurityPolicy;
+import org.apache.archiva.redback.users.*;
+import org.apache.archiva.redback.users.jpa.model.JpaUser;
+import org.apache.commons.lang.StringUtils;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import javax.persistence.*;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Created by martin on 20.09.16.
+ */
+@org.springframework.stereotype.Service("userManager#jpa")
+public class JpaUserManager extends AbstractUserManager {
+
+
+ @PersistenceContext(unitName = "redback-jpa")
+ EntityManager em;
+
+ @Inject
+ private UserSecurityPolicy userSecurityPolicy;
+
+ // JpaUserManager is a singleton and initialization should be thread safe
+ private AtomicBoolean initialized = new AtomicBoolean(false);
+
+
+ public void setEntityManager(EntityManager em) {
+ this.em = em;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ @Override
+ public String getId() {
+ return "jpa";
+ }
+
+ private EntityManager getEm() {
+ if (initialized.compareAndSet(false,true)) {
+ Query q = em.createQuery("SELECT COUNT(u.username) FROM JpaUser u");
+ boolean dbInit = q.getFirstResult()==0;
+ fireUserManagerInit(dbInit);
+ }
+ return em;
+ }
+
+
+ @Override
+ public User createUser(String username, String fullName, String emailAddress) throws UserManagerException {
+
+ JpaUser user = new JpaUser();
+ user.setUsername(username);
+ user.setFullName(fullName);
+ user.setEmail(emailAddress);
+ return user;
+ }
+
+ @Override
+ public UserQuery createUserQuery() {
+ return new JpaUserQuery();
+ }
+
+ @Override
+ public List getUsers() throws UserManagerException {
+ final EntityManager em = getEm();
+ Query q= em.createQuery("SELECT x from JpaUser x");
+ return q.getResultList();
+ }
+
+ @Override
+ public List getUsers(boolean orderAscending) throws UserManagerException {
+ final EntityManager em = getEm();
+ final String orderFlag = orderAscending ? "ASC" : "DESC";
+ Query q = em.createQuery("SELECT u FROM JpaUser u ORDER BY u.username "+orderFlag);
+ return q.getResultList();
+ }
+
+ @Override
+ public User addUser(User user) throws UserManagerException {
+ EntityManager em = getEm();
+ if ( !( user instanceof JpaUser ) )
+ {
+ throw new UserManagerException( "Unable to Add User. User object " + user.getClass().getName() +
+ " is not an instance of " + JpaUser.class.getName() );
+ }
+
+ if ( StringUtils.isEmpty( user.getUsername() ) )
+ {
+ throw new IllegalStateException(
+ Messages.getString( "user.manager.cannot.add.user.without.username" ) ); //$NON-NLS-1$
+ }
+
+ userSecurityPolicy.extensionChangePassword( user );
+
+ fireUserManagerUserAdded( user );
+
+ // TODO: find a better solution
+ // workaround for avoiding the admin from providing another password on the next login after the
+ // admin account has been created
+ // extensionChangePassword by default sets the password change status to false
+ if ( "admin".equals( user.getUsername() ) )
+ {
+ user.setPasswordChangeRequired( false );
+ }
+ else
+ {
+ user.setPasswordChangeRequired( true );
+ }
+ em.getTransaction().begin();
+ em.persist((JpaUser)user);
+ em.getTransaction().commit();
+ return user;
+ }
+
+ @Override
+ public User updateUser(User user) throws UserNotFoundException, UserManagerException {
+ return updateUser(user, false);
+ }
+
+ @Override
+ public User findUser(String username) throws UserNotFoundException, UserManagerException {
+ if (username==null) {
+ throw new UserNotFoundException("Username was ");
+ }
+ final EntityManager em = getEm();
+ TypedQuery q = em.createQuery("SELECT u FROM JpaUser u WHERE LOWER(u.username)=:uname", JpaUser.class);
+ q.setParameter("uname",username.toLowerCase());
+ User result;
+ try {
+ result = q.getSingleResult();
+ } catch (NoResultException ex ) {
+ throw new UserNotFoundException(ex);
+ }
+ return result;
+ }
+
+ @Override
+ public User findUser(String username, boolean useCache) throws UserNotFoundException, UserManagerException {
+ return findUser(username);
+ }
+
+ @Override
+ public List findUsersByUsernameKey(String usernameKey, boolean orderAscending) throws UserManagerException {
+ return findUsers("username",usernameKey,"username",orderAscending);
+ }
+
+ @Override
+ public List findUsersByFullNameKey(String fullNameKey, boolean orderAscending) throws UserManagerException {
+ return findUsers("fullName",fullNameKey,"username",orderAscending);
+ }
+
+ @Override
+ public List findUsersByEmailKey(String emailKey, boolean orderAscending) throws UserManagerException {
+ return findUsers("email",emailKey,"username", orderAscending);
+ }
+
+ @Override
+ public List findUsersByQuery(final UserQuery queryParam) throws UserManagerException {
+ final EntityManager em = getEm();
+ final JpaUserQuery query = (JpaUserQuery)queryParam;
+ String orderByAttribute = "";
+ if (UserQuery.ORDER_BY_EMAIL.equals(query.getOrderBy())) {
+ orderByAttribute="email";
+ } else if (UserQuery.ORDER_BY_FULLNAME.equals(query.getOrderBy())) {
+ orderByAttribute="fullName";
+ } else if (UserQuery.ORDER_BY_USERNAME.equals(query.getOrderBy())) {
+ orderByAttribute="username";
+ } else {
+ throw new IllegalArgumentException("Unknown order attribute "+query.getOrderBy());
+ }
+ StringBuilder sb = new StringBuilder("SELECT u FROM JpaUser u ");
+ if (query.hasUsername()||query.hasFullName()||query.hasEmail()) {
+ sb.append("WHERE ");
+ }
+ boolean checkBefore = false;
+ if (query.hasUsername()) {
+ sb.append("LOWER(u.username) LIKE :username ");
+ checkBefore=true;
+ }
+ if (query.hasEmail()) {
+ if (checkBefore) {
+ sb.append("AND ");
+ }
+ checkBefore=true;
+ sb.append("LOWER(u.email) LIKE :email ");
+ }
+ if (query.hasFullName()) {
+ if (checkBefore) {
+ sb.append("AND ");
+ }
+ sb.append("LOWER(u.fullName) LIKE :fullname ");
+ }
+ if (query.getOrderBy()!=null && !"".equals(query.getOrderBy())) {
+ sb.append("ORDER BY u.").append(orderByAttribute).append(query.isAscending() ? " ASC" : " DESC");
+ }
+ TypedQuery q = em.createQuery(sb.toString(), User.class);
+ if (query.hasUsername()) {
+ q.setParameter("username", "%"+query.getUsername().toLowerCase()+"%");
+ }
+ if (query.hasEmail()) {
+ q.setParameter("email", "%"+query.getEmail().toLowerCase()+"%");
+ }
+ if (query.hasFullName()) {
+ q.setParameter("fullname", "%"+query.getFullName().toLowerCase()+"%");
+ }
+ q.setFirstResult((int)query.getFirstResult()).setMaxResults((int)query.getMaxResults());
+ return q.getResultList();
+ }
+
+ private List findUsers(final String attribute, final String pattern,
+ final String orderAttribute, final boolean orderAscending) {
+ final EntityManager em = getEm();
+ StringBuilder sb = new StringBuilder("SELECT u FROM JpaUser u WHERE LOWER(u.");
+ sb.append(attribute).append(") LIKE :patternvalue ORDER BY u.").append(orderAttribute);
+ sb.append(orderAscending ? " ASC" : " DESC");
+ TypedQuery q = em.createQuery(sb.toString(),User.class);
+ q.setParameter("patternvalue","%"+pattern.toLowerCase()+"%");
+ return q.getResultList();
+ }
+
+ @Override
+ public boolean userExists(String principal) throws UserManagerException {
+ EntityManager em = getEm();
+ JpaUser user = em.find(JpaUser.class, principal);
+ return user != null;
+ }
+
+
+
+ @Override
+ public void deleteUser(String username) throws UserNotFoundException, UserManagerException {
+ final EntityManager em = getEm();
+ User u = findUser(username);
+ if (u.isPermanent()) {
+ throw new PermanentUserException("User "+username+" cannot be deleted");
+ }
+ em.getTransaction().begin();
+ em.remove(u);
+ em.getTransaction().commit();
+ fireUserManagerUserRemoved(u);
+ }
+
+ @Override
+ public void addUserUnchecked(User user) throws UserManagerException {
+
+ }
+
+ @Override
+ public void eraseDatabase() {
+ EntityManager em = getEm();
+ em.getTransaction().begin();
+ Query q = em.createQuery("DELETE FROM JpaUser u");
+ q.executeUpdate();
+ em.getTransaction().commit();
+ }
+
+ @Override
+ public User updateUser(User user, boolean passwordChangeRequired) throws UserNotFoundException, UserManagerException {
+ if ( StringUtils.isNotEmpty( user.getPassword() ) )
+ {
+ userSecurityPolicy.extensionChangePassword( user, passwordChangeRequired );
+ }
+ final EntityManager em = getEm();
+ em.getTransaction().begin();
+ em.persist((JpaUser)user);
+ em.getTransaction().commit();
+ fireUserManagerUserUpdated(user);
+ return user;
+ }
+
+ @Override
+ public String getDescriptionKey() {
+ return null;
+ }
+
+
+
+
+}
diff --git a/redback-users/redback-users-providers/redback-users-jpa/src/main/java/org/apache/archiva/redback/users/jpa/JpaUserQuery.java b/redback-users/redback-users-providers/redback-users-jpa/src/main/java/org/apache/archiva/redback/users/jpa/JpaUserQuery.java
new file mode 100644
index 00000000..d48a59ce
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-jpa/src/main/java/org/apache/archiva/redback/users/jpa/JpaUserQuery.java
@@ -0,0 +1,123 @@
+package org.apache.archiva.redback.users.jpa;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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.
+ */
+
+import org.apache.archiva.redback.users.UserQuery;
+
+import java.util.Arrays;
+
+/**
+ * Created by martin on 23.09.16.
+ */
+public class JpaUserQuery implements UserQuery {
+
+ private String username;
+ private String email;
+ private String fullName;
+ private long firstResult=0;
+ private long maxResults=Integer.MAX_VALUE;
+ private boolean ascending=true;
+
+ private String orderBy="username";
+
+ @Override
+ public String getUsername() {
+ return username;
+ }
+
+ @Override
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public boolean hasUsername() {
+ return username != null && !"".equals(username);
+ }
+
+ @Override
+ public String getEmail() {
+ return email;
+ }
+
+ @Override
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public boolean hasEmail() {
+ return email!=null && !"".equals(email);
+ }
+
+ @Override
+ public String getFullName() {
+ return fullName;
+ }
+
+ @Override
+ public void setFullName(String fullName) {
+ this.fullName = fullName;
+ }
+
+ public boolean hasFullName() {
+ return fullName!=null && !"".equals(fullName);
+ }
+
+ @Override
+ public long getFirstResult() {
+ return firstResult;
+ }
+
+ public void setFirstResult(int firstResult) {
+ this.firstResult = firstResult;
+ }
+
+ @Override
+ public long getMaxResults() {
+ return maxResults;
+ }
+
+ public void setMaxResults(int maxResults) {
+ this.maxResults = maxResults;
+ }
+
+ @Override
+ public boolean isAscending() {
+ return ascending;
+ }
+
+ @Override
+ public void setAscending(boolean ascending) {
+ this.ascending = ascending;
+ }
+
+
+ @Override
+ public String getOrderBy() {
+ return orderBy;
+ }
+
+ @Override
+ public void setOrderBy(String orderBy) {
+ if (!UserQuery.ALLOWED_ORDER_FIELDS.contains(orderBy)) {
+ throw new IllegalArgumentException("Order attribute not allowed: "+orderBy);
+ }
+ this.orderBy = orderBy;
+ }
+}
diff --git a/redback-users/redback-users-providers/redback-users-jpa/src/main/java/org/apache/archiva/redback/users/jpa/model/JpaUser.java b/redback-users/redback-users-providers/redback-users-jpa/src/main/java/org/apache/archiva/redback/users/jpa/model/JpaUser.java
new file mode 100644
index 00000000..04f441e0
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-jpa/src/main/java/org/apache/archiva/redback/users/jpa/model/JpaUser.java
@@ -0,0 +1,206 @@
+package org.apache.archiva.redback.users.jpa.model;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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.
+ */
+
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Created by martin on 20.09.16.
+ */
+@Entity
+@Table(name="JDOUSER")
+public class JpaUser implements org.apache.archiva.redback.users.User {
+
+ @Id
+ private String username;
+
+ private String fullName;
+ private String email;
+ private String encodedPassword;
+ private Date lastPasswordChange;
+ @ElementCollection
+ private List previousEncodedPasswords = new ArrayList();
+ private boolean permanent;
+ private boolean locked;
+ private boolean passwordChangeRequired;
+ private boolean validated;
+ private int countFailedLoginAttempts;
+ private Date accountCreationDate;
+ private Date lastLoginDate;
+ private String rawPassword;
+
+
+ @Override
+ public String getUsername() {
+ return username;
+ }
+
+ @Override
+ public void setUsername(String name) {
+ this.username = name;
+ }
+
+ @Override
+ public String getFullName() {
+ return fullName;
+ }
+
+ @Override
+ public void setFullName(String name) {
+ this.fullName = name;
+ }
+
+ @Override
+ public String getEmail() {
+ return email;
+ }
+
+ @Override
+ public void setEmail(String address) {
+ this.email = address;
+ }
+
+ @Override
+ public String getPassword() {
+ return rawPassword;
+ }
+
+ @Override
+ public void setPassword(String rawPassword) {
+ this.rawPassword = rawPassword;
+ }
+
+ @Override
+ public String getEncodedPassword() {
+ return encodedPassword;
+ }
+
+ @Override
+ public void setEncodedPassword(String encodedPassword) {
+ this.encodedPassword = encodedPassword;
+ }
+
+ @Override
+ public Date getLastPasswordChange() {
+ return lastPasswordChange;
+ }
+
+ @Override
+ public void setLastPasswordChange(Date passwordChangeDate) {
+ this.lastPasswordChange = lastPasswordChange;
+ }
+
+ @Override
+ public List getPreviousEncodedPasswords() {
+ return previousEncodedPasswords;
+ }
+
+ @Override
+ public void setPreviousEncodedPasswords(List encodedPasswordList) {
+ this.previousEncodedPasswords.clear();
+ this.previousEncodedPasswords.addAll(encodedPasswordList);
+ }
+
+ @Override
+ public void addPreviousEncodedPassword(String encodedPassword) {
+ this.previousEncodedPasswords.add(encodedPassword);
+ }
+
+ @Override
+ public boolean isPermanent() {
+ return permanent;
+ }
+
+ @Override
+ public void setPermanent(boolean permanent) {
+ this.permanent = permanent;
+ }
+
+ @Override
+ public boolean isLocked() {
+ return locked;
+ }
+
+ @Override
+ public void setLocked(boolean locked) {
+ this.locked = locked;
+ }
+
+ @Override
+ public boolean isPasswordChangeRequired() {
+ return passwordChangeRequired;
+ }
+
+ @Override
+ public void setPasswordChangeRequired(boolean changeRequired) {
+ this.passwordChangeRequired = changeRequired;
+ }
+
+ @Override
+ public boolean isValidated() {
+ return validated;
+ }
+
+ @Override
+ public void setValidated(boolean valid) {
+ this.validated = valid;
+ }
+
+ @Override
+ public int getCountFailedLoginAttempts() {
+ return countFailedLoginAttempts;
+ }
+
+ @Override
+ public void setCountFailedLoginAttempts(int count) {
+ this.countFailedLoginAttempts = count;
+ }
+
+ @Override
+ public Date getAccountCreationDate() {
+ return accountCreationDate;
+ }
+
+ @Override
+ public void setAccountCreationDate(Date date) {
+ this.accountCreationDate = date;
+ }
+
+ @Override
+ public Date getLastLoginDate() {
+ return lastLoginDate;
+ }
+
+ @Override
+ public void setLastLoginDate(Date date) {
+ this.lastLoginDate = date;
+ }
+
+ @Override
+ public String getUserManagerId() {
+ return null;
+ }
+}
diff --git a/redback-users/redback-users-providers/redback-users-jpa/src/test/java/org/apache/archiva/redback/users/jpa/JpaUserManagerTest.java b/redback-users/redback-users-providers/redback-users-jpa/src/test/java/org/apache/archiva/redback/users/jpa/JpaUserManagerTest.java
new file mode 100644
index 00000000..4556755f
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-jpa/src/test/java/org/apache/archiva/redback/users/jpa/JpaUserManagerTest.java
@@ -0,0 +1,86 @@
+package org.apache.archiva.redback.users.jpa;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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.
+ */
+
+import org.apache.archiva.redback.policy.UserSecurityPolicy;
+import org.apache.archiva.redback.users.User;
+import org.apache.archiva.redback.users.UserManager;
+import org.apache.archiva.redback.users.provider.test.AbstractUserManagerTestCase;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Created by martin on 21.09.16.
+ */
+
+public class JpaUserManagerTest extends AbstractUserManagerTestCase {
+
+ Log log = LogFactory.getLog(JpaUserManagerTest.class);
+
+ @Inject
+ @Named("userManager#jpa")
+ JpaUserManager jpaUserManager;
+
+
+ @Inject
+ private UserSecurityPolicy securityPolicy;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+
+ super.setUp();
+ Properties props = new Properties();
+ InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("test.properties");
+ assert is!=null;
+ props.load(is);
+ is.close();
+ EntityManagerFactory emf = Persistence.createEntityManagerFactory("redback-jpa",props);
+
+ log.info("test setup");
+ jpaUserManager.setEntityManager(emf.createEntityManager());
+ super.setUserManager(jpaUserManager);
+ assertNotNull(jpaUserManager);
+ log.info("injected usermanager "+jpaUserManager);
+
+ // create the factory defined by the "openjpa" entity-manager entry
+
+ }
+
+ @Test
+ public void testInit() {
+ jpaUserManager.initialize();
+ }
+
+
+
+
+}
diff --git a/redback-users/redback-users-providers/redback-users-jpa/src/test/resources/spring-context.xml b/redback-users/redback-users-providers/redback-users-jpa/src/test/resources/spring-context.xml
new file mode 100644
index 00000000..4d7802ed
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-jpa/src/test/resources/spring-context.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ]]>
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/redback-users/redback-users-providers/redback-users-jpa/src/test/resources/test.properties b/redback-users/redback-users-providers/redback-users-jpa/src/test/resources/test.properties
new file mode 100644
index 00000000..df848c45
--- /dev/null
+++ b/redback-users/redback-users-providers/redback-users-jpa/src/test/resources/test.properties
@@ -0,0 +1,22 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF 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.
+openjpa.ConnectionURL=jdbc:hsqldb:mem:reback-jpa
+openjpa.ConnectionDriverName=org.hsqldb.jdbcDriver
+openjpa.ConnectionUserName=sa
+openjpa.ConnectionPassword=
+openjp.Log=DefaultLevel=WARN,Tool=INFO
+openjpa.jdbc.SynchronizeMappings=buildSchema(ForeignKeys=true)
\ No newline at end of file
diff --git a/redback-users/redback-users-tests/src/main/java/org/apache/archiva/redback/users/provider/test/AbstractUserManagerTestCase.java b/redback-users/redback-users-tests/src/main/java/org/apache/archiva/redback/users/provider/test/AbstractUserManagerTestCase.java
index 6da8ac8d..d293bda5 100644
--- a/redback-users/redback-users-tests/src/main/java/org/apache/archiva/redback/users/provider/test/AbstractUserManagerTestCase.java
+++ b/redback-users/redback-users-tests/src/main/java/org/apache/archiva/redback/users/provider/test/AbstractUserManagerTestCase.java
@@ -97,10 +97,12 @@ public class AbstractUserManagerTestCase
throws UserManagerException
{
+ assertNotNull(userManager);
getUserManager().eraseDatabase();
getEventTracker().userManagerInit( true );
assertNotNull( getUserManager() );
+ assertNotNull(userManager.getUsers());
assertEquals( "New UserManager should contain no users. " + userManager.getUsers(), 0,
userManager.getUsers().size() );
}