From d754032e0502c3bf40ec499083b36121a266207c Mon Sep 17 00:00:00 2001
From: izabel <izabel>
Date: Tue, 28 Jul 2009 12:55:33 +0000
Subject: [PATCH] [LMS-1066] authorization groups db (2)

SVN: 11910
---
 .../dataaccess/IAuthorizationGroupDAO.java    |  39 ++++
 .../dataaccess/db/AuthorizationGroupDAO.java  |  77 ++++++++
 .../dataaccess/db/DatabaseVersionHolder.java  |   2 +-
 .../shared/dto/AuthorizationGroupPE.java      | 182 ++++++++++++++++++
 .../generic/shared/dto/ColumnNames.java       |   6 +
 .../generic/shared/dto/RoleAssignmentPE.java  |  45 +++--
 .../generic/shared/dto/SequenceNames.java     |   7 +-
 .../generic/shared/dto/TableNames.java        |   4 +
 openbis/source/java/hibernateContext.xml      |   1 +
 openbis/source/sql/generic/039/schema-039.sql |   8 +-
 .../migration/migration-038-039.sql           |   6 +-
 11 files changed, 361 insertions(+), 16 deletions(-)
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IAuthorizationGroupDAO.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/AuthorizationGroupDAO.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AuthorizationGroupPE.java

diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IAuthorizationGroupDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IAuthorizationGroupDAO.java
new file mode 100644
index 00000000000..b9a7e08d580
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IAuthorizationGroupDAO.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.server.dataaccess;
+
+import java.util.List;
+
+import ch.systemsx.cisd.openbis.generic.shared.dto.AuthorizationGroupPE;
+
+/**
+ * <i>Data Access Object</i> for {@link AuthorizationGroupPE}.
+ * 
+ * @author Izabela Adamczyk
+ */
+public interface IAuthorizationGroupDAO extends IGenericDAO<AuthorizationGroupPE>
+{
+    /**
+     * Lists all authorization groups.
+     */
+    public List<AuthorizationGroupPE> list();
+
+    /**
+     * Creates a new authorization group.
+     */
+    public void create(AuthorizationGroupPE authorizationGroup);
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/AuthorizationGroupDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/AuthorizationGroupDAO.java
new file mode 100644
index 00000000000..816e2e4aabb
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/AuthorizationGroupDAO.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.server.dataaccess.db;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.hibernate.SessionFactory;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+
+import ch.systemsx.cisd.common.logging.LogCategory;
+import ch.systemsx.cisd.common.logging.LogFactory;
+import ch.systemsx.cisd.common.utilities.MethodUtils;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IAuthorizationGroupDAO;
+import ch.systemsx.cisd.openbis.generic.shared.dto.AuthorizationGroupPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.CodeConverter;
+import ch.systemsx.cisd.openbis.generic.shared.dto.DatabaseInstancePE;
+
+/**
+ * Implementation of {@link IAuthorizationGroupDAO}.
+ * 
+ * @author Izabela Adamczyk
+ */
+public class AuthorizationGroupDAO extends AbstractGenericEntityDAO<AuthorizationGroupPE> implements
+        IAuthorizationGroupDAO
+{
+    private static final Logger operationLog =
+            LogFactory.getLogger(LogCategory.OPERATION, AuthorizationGroupDAO.class);
+
+    protected AuthorizationGroupDAO(final SessionFactory sessionFactory,
+            final DatabaseInstancePE databaseInstance)
+    {
+        super(sessionFactory, databaseInstance, AuthorizationGroupPE.class);
+    }
+
+    public List<AuthorizationGroupPE> list()
+    {
+        final List<AuthorizationGroupPE> list =
+                cast(getHibernateTemplate().loadAll(AuthorizationGroupPE.class));
+        if (operationLog.isDebugEnabled())
+        {
+            operationLog.debug(String.format("%s(): %d authorization group(s) have been found.",
+                    MethodUtils.getCurrentMethod().getName(), list.size()));
+        }
+        return list;
+    }
+
+    public void create(AuthorizationGroupPE authorizationGroup)
+    {
+        assert authorizationGroup != null : "Missing authorization group.";
+        validatePE(authorizationGroup);
+
+        authorizationGroup.setCode(CodeConverter.tryToDatabase(authorizationGroup.getCode()));
+        final HibernateTemplate template = getHibernateTemplate();
+        template.saveOrUpdate(authorizationGroup);
+        template.flush();
+        if (operationLog.isInfoEnabled())
+        {
+            operationLog.info(String.format("SAVE: authorization group '%s'.", authorizationGroup));
+        }
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DatabaseVersionHolder.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DatabaseVersionHolder.java
index f38488f50aa..3a419d608e1 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DatabaseVersionHolder.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/DatabaseVersionHolder.java
@@ -24,7 +24,7 @@ package ch.systemsx.cisd.openbis.generic.server.dataaccess.db;
 public final class DatabaseVersionHolder
 {
     /** Current version of the database. */
-    private static final String DATABASE_VERSION = "038";
+    private static final String DATABASE_VERSION = "039";
 
     private DatabaseVersionHolder()
     {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AuthorizationGroupPE.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AuthorizationGroupPE.java
new file mode 100644
index 00000000000..89b4171c9fc
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/AuthorizationGroupPE.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2009 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.dto;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
+import javax.persistence.Version;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.Index;
+import org.hibernate.search.annotations.Store;
+import org.hibernate.validator.Length;
+import org.hibernate.validator.NotNull;
+import org.hibernate.validator.Pattern;
+
+import ch.systemsx.cisd.common.utilities.ModifiedShortPrefixToStringStyle;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.GenericConstants;
+import ch.systemsx.cisd.openbis.generic.shared.IServer;
+import ch.systemsx.cisd.openbis.generic.shared.dto.hibernate.SearchFieldConstants;
+
+/**
+ * Persistent Entity containing informations about authorization group.
+ * 
+ * @author Izabela Adamczyk
+ */
+@Entity
+@Table(name = TableNames.AUTHORIZATION_GROUPS_TABLE)
+public class AuthorizationGroupPE extends HibernateAbstractRegistrationHolder implements
+        Comparable<AuthorizationGroupPE>, IIdAndCodeHolder, Serializable
+{
+    private static final long serialVersionUID = IServer.VERSION;
+
+    private transient Long id;
+
+    private DatabaseInstancePE databaseInstance;
+
+    private String code;
+
+    private String description;
+
+    private Date modificationDate;
+
+    public AuthorizationGroupPE()
+    {
+    }
+
+    @SequenceGenerator(name = SequenceNames.AUTHORIZATION_GROUP_ID_SEQUENCE, sequenceName = SequenceNames.AUTHORIZATION_GROUP_ID_SEQUENCE, allocationSize = 1)
+    @Id
+    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SequenceNames.AUTHORIZATION_GROUP_ID_SEQUENCE)
+    public final Long getId()
+    {
+        return id;
+    }
+
+    public final void setId(final Long id)
+    {
+        this.id = id;
+    }
+
+    @ManyToOne(fetch = FetchType.EAGER)
+    @NotNull(message = ValidationMessages.DATABASE_INSTANCE_NOT_NULL_MESSAGE)
+    @JoinColumn(name = ColumnNames.DATABASE_INSTANCE_COLUMN, updatable = false)
+    public final DatabaseInstancePE getDatabaseInstance()
+    {
+        return databaseInstance;
+    }
+
+    public final void setDatabaseInstance(final DatabaseInstancePE databaseInstance)
+    {
+        this.databaseInstance = databaseInstance;
+    }
+
+    @NotNull(message = ValidationMessages.CODE_NOT_NULL_MESSAGE)
+    @Length(min = 1, max = 40, message = ValidationMessages.CODE_LENGTH_MESSAGE)
+    @Pattern(regex = AbstractIdAndCodeHolder.CODE_PATTERN, flags = java.util.regex.Pattern.CASE_INSENSITIVE, message = ValidationMessages.CODE_PATTERN_MESSAGE)
+    @Field(index = Index.TOKENIZED, store = Store.YES, name = SearchFieldConstants.CODE)
+    public final String getCode()
+    {
+        return code;
+    }
+
+    public final void setCode(final String code)
+    {
+        this.code = code;
+    }
+
+    @Column(name = ColumnNames.DESCRIPTION_COLUMN)
+    @Length(max = GenericConstants.DESCRIPTION_1000, message = ValidationMessages.DESCRIPTION_LENGTH_MESSAGE)
+    public final String getDescription()
+    {
+        return description;
+    }
+
+    public final void setDescription(final String description)
+    {
+        this.description = description;
+    }
+
+    @Version
+    @Column(name = ColumnNames.MODIFICATION_TIMESTAMP_COLUMN, nullable = false)
+    public Date getModificationDate()
+    {
+        return modificationDate;
+    }
+
+    public void setModificationDate(Date versionDate)
+    {
+        this.modificationDate = versionDate;
+    }
+
+    @Override
+    public final boolean equals(final Object obj)
+    {
+        if (obj == this)
+        {
+            return true;
+        }
+        if (obj instanceof AuthorizationGroupPE == false)
+        {
+            return false;
+        }
+        final AuthorizationGroupPE that = (AuthorizationGroupPE) obj;
+        final EqualsBuilder builder = new EqualsBuilder();
+        builder.append(getCode(), that.getCode());
+        builder.append(getDatabaseInstance(), that.getDatabaseInstance());
+        return builder.isEquals();
+    }
+
+    @Override
+    public final int hashCode()
+    {
+        final HashCodeBuilder builder = new HashCodeBuilder();
+        builder.append(getCode());
+        builder.append(getDatabaseInstance());
+        return builder.toHashCode();
+    }
+
+    @Override
+    public final String toString()
+    {
+        final ToStringBuilder builder =
+                new ToStringBuilder(this,
+                        ModifiedShortPrefixToStringStyle.MODIFIED_SHORT_PREFIX_STYLE);
+        builder.append("code", getCode());
+        builder.append("dbInstance", getDatabaseInstance());
+        return builder.toString();
+    }
+
+    public int compareTo(AuthorizationGroupPE o)
+    {
+        return AbstractIdAndCodeHolder.compare(this, o);
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ColumnNames.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ColumnNames.java
index e5b22d21069..407170c5e42 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ColumnNames.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ColumnNames.java
@@ -204,6 +204,12 @@ public final class ColumnNames
 
     public static final String IS_CHOSEN_FROM_LIST = "is_chosen_from_list";
 
+    public static final String AUTHORIZATION_GROUP_ID_COLUMN = "ag_id";
+
+    public static final String PERSON_ID_COLUMN = "pers_id";
+
+    public static final String AUTHORIZATION_GROUP_ID_GRANTEE_COLUMN = "ag_id_grantee";
+
     private ColumnNames()
     {
         // Can not be instantiated.
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/RoleAssignmentPE.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/RoleAssignmentPE.java
index d068b517e0c..25cda7a07bb 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/RoleAssignmentPE.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/RoleAssignmentPE.java
@@ -31,7 +31,6 @@ import javax.persistence.ManyToOne;
 import javax.persistence.SequenceGenerator;
 import javax.persistence.Table;
 import javax.persistence.Transient;
-import javax.persistence.UniqueConstraint;
 
 import org.apache.commons.lang.builder.EqualsBuilder;
 import org.apache.commons.lang.builder.HashCodeBuilder;
@@ -46,21 +45,20 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.IIdHolder;
 import ch.systemsx.cisd.openbis.generic.shared.util.EqualsHashUtils;
 
 /**
- * Persistent Entity corresponding containing informations about role assignment.
+ * Persistent Entity containing informations about role assignment.
  * 
  * @author Izabela Adamczyk
  */
+
 @Entity
-@Check(constraints = ColumnNames.DATABASE_INSTANCE_COLUMN + " IS NOT NULL AND "
-        + ColumnNames.GROUP_COLUMN + " IS NULL OR " + ColumnNames.DATABASE_INSTANCE_COLUMN
-        + " IS NULL AND " + ColumnNames.GROUP_COLUMN + " IS NOT NULL")
-@Table(name = TableNames.ROLE_ASSIGNMENTS_TABLE, uniqueConstraints =
-    { @UniqueConstraint(columnNames =
-        { ColumnNames.PERSON_GRANTEE_COLUMN, ColumnNames.ROLE_COLUMN, ColumnNames.GROUP_COLUMN,
-                ColumnNames.DATABASE_INSTANCE_COLUMN }) })
+@Check(constraints = "((DBIN_ID IS NOT NULL AND GROU_ID IS NULL) OR (DBIN_ID IS NULL AND GROU_ID IS NOT NULL))"
+        + " AND "
+        + "((AG_ID_GRANTEE IS NOT NULL AND PERS_ID_GRANTEE IS NULL) OR (AG_ID_GRANTEE IS NULL AND PERS_ID_GRANTEE IS NOT NULL))")
+@Table(name = TableNames.ROLE_ASSIGNMENTS_TABLE)
 public final class RoleAssignmentPE extends HibernateAbstractRegistrationHolder implements
         IIdHolder, Serializable
 {
+
     private static final long serialVersionUID = IServer.VERSION;
 
     public static final RoleAssignmentPE[] EMPTY_ARRAY = new RoleAssignmentPE[0];
@@ -73,6 +71,8 @@ public final class RoleAssignmentPE extends HibernateAbstractRegistrationHolder
 
     private PersonPE person;
 
+    private AuthorizationGroupPE authorizationGroup;
+
     private RoleCode role;
 
     @NotNull(message = ValidationMessages.ROLE_NOT_NULL_MESSAGE)
@@ -88,7 +88,6 @@ public final class RoleAssignmentPE extends HibernateAbstractRegistrationHolder
         this.role = role;
     }
 
-    @NotNull(message = ValidationMessages.PERSON_NOT_NULL_MESSAGE)
     @ManyToOne(fetch = FetchType.EAGER)
     @JoinColumn(name = ColumnNames.PERSON_GRANTEE_COLUMN, updatable = false)
     @Private
@@ -103,12 +102,32 @@ public final class RoleAssignmentPE extends HibernateAbstractRegistrationHolder
         this.person = person;
     }
 
+    @ManyToOne(fetch = FetchType.EAGER)
+    @JoinColumn(name = ColumnNames.AUTHORIZATION_GROUP_ID_GRANTEE_COLUMN, updatable = false)
+    @Private
+    public final AuthorizationGroupPE getAuthorizationGroupInternal()
+    {
+        return authorizationGroup;
+    }
+
+    @Private
+    public final void setAuthorizationGroupInternal(final AuthorizationGroupPE authorizationGroup)
+    {
+        this.authorizationGroup = authorizationGroup;
+    }
+
     @Transient
     public final PersonPE getPerson()
     {
         return getPersonInternal();
     }
 
+    @Transient
+    public final AuthorizationGroupPE getAuthorizationGroup()
+    {
+        return getAuthorizationGroupInternal();
+    }
+
     public final void setId(final Long id)
     {
         this.id = id;
@@ -158,7 +177,10 @@ public final class RoleAssignmentPE extends HibernateAbstractRegistrationHolder
     public final boolean equals(final Object obj)
     {
         EqualsHashUtils.assertDefined(getRole(), "role");
-        EqualsHashUtils.assertDefined(getPerson(), "person");
+        if (getPerson() == null)
+        {
+            EqualsHashUtils.assertDefined(getAuthorizationGroupInternal(), "authorization group");
+        }
         if (getGroup() == null)
         {
             EqualsHashUtils.assertDefined(getDatabaseInstance(), "db");
@@ -175,6 +197,7 @@ public final class RoleAssignmentPE extends HibernateAbstractRegistrationHolder
         final EqualsBuilder builder = new EqualsBuilder();
         builder.append(getRole(), that.getRole());
         builder.append(getPerson(), that.getPerson());
+        builder.append(getAuthorizationGroup(), that.getAuthorizationGroup());
         builder.append(getDatabaseInstance(), that.getDatabaseInstance());
         builder.append(getGroup(), that.getGroup());
         return builder.isEquals();
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SequenceNames.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SequenceNames.java
index feb3d8c2fea..54b9cfda389 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SequenceNames.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/SequenceNames.java
@@ -39,7 +39,7 @@ public final class SequenceNames
     public static final String DATA_STORE_SEQUENCE = "DATA_STORE_ID_SEQ";
 
     public static final String DATA_STORE_SERVICE_SEQUENCE = "DATA_STORE_SERVICES_ID_SEQ";
-    
+
     public static final String DATA_TYPE_SEQUENCE = "DATA_TYPE_ID_SEQ";
 
     public static final String DATABASE_INSTANCE_SEQUENCE = "DATABASE_INSTANCE_ID_SEQ";
@@ -102,6 +102,11 @@ public final class SequenceNames
 
     public static final String CODE_SEQUENCE = "CODE_SEQ";
 
+    public static final String AUTHORIZATION_GROUP_ID_SEQUENCE = "AUTHORIZATION_GROUP_ID_SEQ";
+
+    public static final String AUTHORIZATION_GROUP_PERSON_ID_SEQUENCE =
+            "AUTHORIZATION_GROUP_PERSON_ID_SEQ";
+
     private SequenceNames()
     {
         // Can not be instantiated.
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/TableNames.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/TableNames.java
index 03402a73c7c..dfaf93bbe7b 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/TableNames.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/TableNames.java
@@ -106,6 +106,10 @@ public final class TableNames
 
     public static final String EVENTS_TABLE = "events";
 
+    public static final String AUTHORIZATION_GROUPS_TABLE = "authorization_groups";
+
+    public static final String AUTHORIZATION_GROUP_PERSONS_TABLE = "authorization_group_persons";
+
     private TableNames()
     {
         // This class can not be instantiated.
diff --git a/openbis/source/java/hibernateContext.xml b/openbis/source/java/hibernateContext.xml
index 7586d7783f7..adaa9fdc67e 100644
--- a/openbis/source/java/hibernateContext.xml
+++ b/openbis/source/java/hibernateContext.xml
@@ -73,6 +73,7 @@
                 <value>ch.systemsx.cisd.openbis.generic.shared.dto.DataSetTypePropertyTypePE</value>
                 
                 <value>ch.systemsx.cisd.openbis.generic.shared.dto.AttachmentHolderPE</value>
+                <value>ch.systemsx.cisd.openbis.generic.shared.dto.AuthorizationGroupPE</value>
             </list>
         </property>
         <!-- 
diff --git a/openbis/source/sql/generic/039/schema-039.sql b/openbis/source/sql/generic/039/schema-039.sql
index 344b9e9cfbf..1c633323e64 100644
--- a/openbis/source/sql/generic/039/schema-039.sql
+++ b/openbis/source/sql/generic/039/schema-039.sql
@@ -72,7 +72,7 @@ CREATE TABLE SAMPLE_TYPE_PROPERTY_TYPES (ID TECH_ID NOT NULL,SATY_ID TECH_ID NOT
 CREATE TABLE DATA_SET_PROPERTIES (ID TECH_ID NOT NULL,DS_ID TECH_ID NOT NULL,DSTPT_ID TECH_ID NOT NULL,VALUE GENERIC_VALUE,CVTE_ID TECH_ID, MATE_PROP_ID TECH_ID, PERS_ID_REGISTERER TECH_ID NOT NULL,REGISTRATION_TIMESTAMP TIME_STAMP_DFL NOT NULL DEFAULT CURRENT_TIMESTAMP, MODIFICATION_TIMESTAMP TIME_STAMP DEFAULT CURRENT_TIMESTAMP);
 CREATE TABLE DATA_SET_TYPE_PROPERTY_TYPES (ID TECH_ID NOT NULL,DSTY_ID TECH_ID NOT NULL,PRTY_ID TECH_ID NOT NULL,IS_MANDATORY BOOLEAN_CHAR NOT NULL DEFAULT 'F',IS_MANAGED_INTERNALLY BOOLEAN_CHAR NOT NULL DEFAULT 'F',PERS_ID_REGISTERER TECH_ID NOT NULL,REGISTRATION_TIMESTAMP TIME_STAMP_DFL NOT NULL DEFAULT CURRENT_TIMESTAMP);
 
-CREATE TABLE AUTHORIZATION_GROUPS (ID TECH_ID NOT NULL, DBIN_ID TECH_ID NOT NULL, CODE CODE NOT NULL, DESCRIPTION DESCRIPTION_1000);
+CREATE TABLE AUTHORIZATION_GROUPS (ID TECH_ID NOT NULL, DBIN_ID TECH_ID NOT NULL, CODE CODE NOT NULL, DESCRIPTION DESCRIPTION_1000,REGISTRATION_TIMESTAMP TIME_STAMP_DFL NOT NULL DEFAULT CURRENT_TIMESTAMP, PERS_ID_REGISTERER TECH_ID NOT NULL, MODIFICATION_TIMESTAMP TIME_STAMP DEFAULT CURRENT_TIMESTAMP);
 CREATE TABLE AUTHORIZATION_GROUP_PERSONS (ID TECH_ID NOT NULL, AG_ID TECH_ID NOT NULL, PERS_ID TECH_ID NOT NULL);
 
 -- Creating sequences
@@ -306,6 +306,7 @@ ALTER TABLE DATA_SET_PROPERTIES ADD CONSTRAINT DSPR_MAPR_FK FOREIGN KEY (MATE_PR
 ALTER TABLE AUTHORIZATION_GROUPS ADD CONSTRAINT AG_DBIN_FK FOREIGN KEY (DBIN_ID) REFERENCES DATABASE_INSTANCES(ID);
 ALTER TABLE AUTHORIZATION_GROUP_PERSONS ADD CONSTRAINT AGP_AG_FK FOREIGN KEY (AG_ID) REFERENCES AUTHORIZATION_GROUPS(ID);
 ALTER TABLE AUTHORIZATION_GROUP_PERSONS ADD CONSTRAINT AGP_PERS_FK FOREIGN KEY (PERS_ID) REFERENCES PERSONS(ID);
+ALTER TABLE AUTHORIZATION_GROUPS ADD CONSTRAINT AG_PERS_FK FOREIGN KEY (PERS_ID_REGISTERER) REFERENCES PERSONS(ID);
 
 -- Creating check constraints
 
@@ -426,3 +427,8 @@ CREATE INDEX DSPR_PERS_FK_I ON DATA_SET_PROPERTIES (PERS_ID_REGISTERER);
 CREATE INDEX DSTPT_DSTY_FK_I ON DATA_SET_TYPE_PROPERTY_TYPES (DSTY_ID);
 CREATE INDEX DSTPT_PERS_FK_I ON DATA_SET_TYPE_PROPERTY_TYPES (PERS_ID_REGISTERER);
 CREATE INDEX DSTPT_PRTY_FK_I ON DATA_SET_TYPE_PROPERTY_TYPES (PRTY_ID);
+
+CREATE UNIQUE INDEX ROAS_ROLE_GROUP_DBI_AG_PERS_UQ_I ON ROLE_ASSIGNMENTS (ROLE_CODE, COALESCE(PERS_ID_GRANTEE,-1), COALESCE(AG_ID_GRANTEE,-1), COALESCE(GROU_ID,-1), COALESCE(DBIN_ID,-1));
+
+
+
diff --git a/openbis/source/sql/postgresql/migration/migration-038-039.sql b/openbis/source/sql/postgresql/migration/migration-038-039.sql
index ec82ebd800f..ac11c950e89 100644
--- a/openbis/source/sql/postgresql/migration/migration-038-039.sql
+++ b/openbis/source/sql/postgresql/migration/migration-038-039.sql
@@ -23,10 +23,11 @@ ALTER TABLE CONTROLLED_VOCABULARY_TERMS ADD COLUMN description DESCRIPTION_1000;
 
 -- Add authorization groups
 
-CREATE TABLE AUTHORIZATION_GROUPS (ID TECH_ID NOT NULL, DBIN_ID TECH_ID NOT NULL, CODE CODE NOT NULL, DESCRIPTION DESCRIPTION_1000);
+CREATE TABLE AUTHORIZATION_GROUPS (ID TECH_ID NOT NULL, DBIN_ID TECH_ID NOT NULL, CODE CODE NOT NULL, DESCRIPTION DESCRIPTION_1000,REGISTRATION_TIMESTAMP TIME_STAMP_DFL NOT NULL DEFAULT CURRENT_TIMESTAMP, PERS_ID_REGISTERER TECH_ID NOT NULL, MODIFICATION_TIMESTAMP TIME_STAMP DEFAULT CURRENT_TIMESTAMP);
 ALTER TABLE AUTHORIZATION_GROUPS ADD CONSTRAINT AG_PK PRIMARY KEY(ID);
 ALTER TABLE AUTHORIZATION_GROUPS ADD CONSTRAINT AG_BK_UK UNIQUE(CODE,DBIN_ID);
 ALTER TABLE AUTHORIZATION_GROUPS ADD CONSTRAINT AG_DBIN_FK FOREIGN KEY (DBIN_ID) REFERENCES DATABASE_INSTANCES(ID);
+ALTER TABLE AUTHORIZATION_GROUPS ADD CONSTRAINT AG_PERS_FK FOREIGN KEY (PERS_ID_REGISTERER) REFERENCES PERSONS(ID);
 CREATE SEQUENCE AUTHORIZATION_GROUP_ID_SEQ;
 
 CREATE TABLE AUTHORIZATION_GROUP_PERSONS (ID TECH_ID NOT NULL, AG_ID TECH_ID NOT NULL, PERS_ID TECH_ID NOT NULL);
@@ -38,12 +39,13 @@ CREATE SEQUENCE AUTHORIZATION_GROUP_PERSON_ID_SEQ;
 
 ALTER TABLE ROLE_ASSIGNMENTS ALTER COLUMN PERS_ID_GRANTEE DROP NOT NULL;
 ALTER TABLE ROLE_ASSIGNMENTS ADD COLUMN AG_ID_GRANTEE TECH_ID;
---TODO: add uniqueness of role assignment
 ALTER TABLE ROLE_ASSIGNMENTS DROP CONSTRAINT ROAS_GROUP_BK_UK;
 ALTER TABLE ROLE_ASSIGNMENTS DROP CONSTRAINT ROAS_INSTANCE_BK_UK;
 ALTER TABLE ROLE_ASSIGNMENTS ADD CONSTRAINT ROAS_AG_PERS_ARC_CK CHECK ((AG_ID_GRANTEE IS NOT NULL AND PERS_ID_GRANTEE IS NULL) OR (AG_ID_GRANTEE IS NULL AND PERS_ID_GRANTEE IS NOT NULL));
 CREATE INDEX ROAS_AG_FK_I_GRANTEE ON ROLE_ASSIGNMENTS (AG_ID_GRANTEE);
 
+CREATE UNIQUE INDEX ROAS_ROLE_GROUP_DBI_AG_PERS_UQ_I ON ROLE_ASSIGNMENTS (ROLE_CODE, COALESCE(PERS_ID_GRANTEE,-1), COALESCE(AG_ID_GRANTEE,-1), COALESCE(GROU_ID,-1), COALESCE(DBIN_ID,-1));
+
 GRANT SELECT ON SEQUENCE AUTHORIZATION_GROUP_ID_SEQ TO GROUP OPENBIS_READONLY;
 GRANT SELECT ON SEQUENCE AUTHORIZATION_GROUP_PERSON_ID_SEQ TO GROUP OPENBIS_READONLY;
 GRANT SELECT ON TABLE AUTHORIZATION_GROUPS TO GROUP OPENBIS_READONLY;
-- 
GitLab