Role.java
/*
* Copyright 2005-2025 the original author or authors.
*
* 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 org.openwms.core.uaa.impl;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import org.ameba.annotation.Default;
import org.springframework.util.Assert;
import java.io.Serializable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* A Role is a group of {@link User}s. Basically more than one {@code User} belong to a Role. Security access policies are assigned to Roles
* instead of {@link User}s.
*
* @author Heiko Scherrer
* @GlossaryTerm
* @see SecurityObject
* @see User
*/
@Entity
@DiscriminatorValue("ROLE")
public class Role extends SecurityObject implements Serializable {
public static final String NOT_ALLOWED_TO_CREATE_A_ROLE_WITH_AN_EMPTY_NAME = "Not allowed to create a Role with an empty name";
/** Whether or not this Role is immutable. Immutable Roles can't be modified. */
@Column(name = "C_IMMUTABLE")
private Boolean immutable = false;
/* ------------------- collection mapping ------------------- */
/**
* All {@link User}s assigned to the Role.
*/
@ManyToMany(cascade = {CascadeType.REFRESH})
@JoinTable(
name = "COR_UAA_ROLE_USER",
joinColumns = @JoinColumn(name = "C_ROLE_ID"),
inverseJoinColumns = @JoinColumn(name = "C_USER_ID"),
foreignKey = @ForeignKey(name = "FK_UAA_ROLE_USER"),
inverseForeignKey = @ForeignKey(name = "FK_UAA_USER_ROLE")
)
private Set<User> users = new HashSet<>();
/**
* All {@link SecurityObject}s assigned to the Role.
*/
@ManyToMany(cascade = {CascadeType.REFRESH})
@JoinTable(
name = "COR_UAA_ROLE_ROLE",
joinColumns = @JoinColumn(name = "C_ROLE_ID"),
inverseJoinColumns = @JoinColumn(name = "C_GRANT_ID"),
foreignKey = @ForeignKey(name = "FK_UAA_ROLE_GRANT"),
inverseForeignKey = @ForeignKey(name = "FK_UAA_GRANT_ROLE")
)
private Set<SecurityObject> grants = new HashSet<>();
/** The default prefix String for each created Role. Name is {@value} . */
public static final String ROLE_PREFIX = "ROLE_";
/**
* A builder class to construct Role instances.
*
* @author Heiko Scherrer
*/
public static class Builder {
private final Role role;
/**
* Create a new Builder.
*
* @param name The name of the Role
* @throws IllegalArgumentException when name is {@literal null} or empty
*/
public Builder(String name) {
Assert.hasText(name, NOT_ALLOWED_TO_CREATE_A_ROLE_WITH_AN_EMPTY_NAME);
role = new Role(name);
role.immutable = false;
}
/**
* Add a description text to the Role.
*
* @param description as String
* @return the builder instance
*/
public Builder withDescription(String description) {
role.setDescription(description);
return this;
}
/**
* Set the Role to be immutable.
*
* @return the builder instance
*/
public Builder asImmutable() {
role.immutable = true;
return this;
}
/**
* Finally build and return the Role instance.
*
* @return the constructed Role
*/
public Role build() {
return role;
}
}
/* ----------------------------- methods ------------------- */
/** Dear JPA... */
protected Role() { }
/**
* Create a new Role with a name.
*
* @param name The name of the Role
* @throws IllegalArgumentException when name is {@literal null} or empty
*/
@Default
public Role(String name) {
Assert.hasText(name, NOT_ALLOWED_TO_CREATE_A_ROLE_WITH_AN_EMPTY_NAME);
setName(normalizeName(name));
}
public String normalizeName(String name) {
if (name != null && !name.startsWith(ROLE_PREFIX)) {
return ROLE_PREFIX + name;
}
return name;
}
/**
* Create a new Role with a name and a description.
*
* @param name The name of the Role
* @param description The description text of the Role
* @throws IllegalArgumentException when name is {@literal null} or empty
*/
public Role(String name, String description) {
Assert.hasText(name, NOT_ALLOWED_TO_CREATE_A_ROLE_WITH_AN_EMPTY_NAME);
setName(normalizeName(name));
setDescription(description);
}
@Override
public void setPersistentKey(String pKey) {
super.setPersistentKey(pKey);
}
/**
* {@inheritDoc}
*/
@Override
protected void setName(String name) {
super.setName(normalizeName(name));
}
/**
* Get the immutable.
*
* @return the immutable.
*/
public Boolean getImmutable() {
return immutable;
}
/**
* Set the Role as immutable.
*
* @param immutable {@code true} if so
*/
// Used by Mapper only
public void setImmutable(Boolean immutable) {
this.immutable = immutable;
}
/**
* Return a Set of all {@link User}s assigned to the Role.
*
* @return A Set of all {@link User}s assigned to the Role
*/
public Set<User> getUsers() {
if (users == null) {
return new HashSet<>();
}
return users;
}
/**
* Add an existing {@link User} to the Role.
*
* @param user The {@link User} to be added
* @return {@literal true} if the {@link User} was new in the collection of {@link User}s, otherwise {@literal false}
* @throws IllegalArgumentException if user is {@literal null}
*/
public boolean addUser(User user) {
Assert.notNull(user, "User to add must not be null");
return users.add(user);
}
/**
* Add existing {@link User}s to the Role.
*
* @param users A Set of {@link User}s to be added to the Role
* @throws IllegalArgumentException if grants is {@literal null}
*/
public void addUsers(Set<User> users) {
Assert.notNull(users, "Set of Users must not be null");
getUsers().addAll(users);
}
/**
* Remove a {@link User} from the Role.
*
* @param user The {@link User} to be removed
* @throws IllegalArgumentException if user is {@literal null}
*/
public void removeUser(User user) {
Assert.notNull(user, "User to remove must not be null");
users.remove(user);
}
/**
* Set all {@link User}s belonging to this Role.
*
* @param users A Set of {@link User}s to be assigned to the Role
* @throws IllegalArgumentException if users is {@literal null}
*/
public void setUsers(Set<User> users) {
Assert.notNull(users, "Set of Users must not be null");
this.users = users;
}
/**
* Return a Set of all {@link SecurityObject}s belonging to the Role.
*
* @return A Set of all {@link SecurityObject}s belonging to this Role
*/
public Set<SecurityObject> getGrants() {
if (grants == null) {
grants = new HashSet<>();
}
return grants;
}
/**
* Add an existing {@link SecurityObject} to the Role.
*
* @param grant The {@link SecurityObject} to be added to the Role.
* @return {@literal true} if the {@link SecurityObject} was new to the collection of {@link SecurityObject}s, otherwise {@literal
* false}
* @throws IllegalArgumentException if grant is {@literal null}
*/
public boolean addGrant(SecurityObject grant) {
Assert.notNull(grant, "Grant to add must not be null");
return grants.add(grant);
}
/**
* Add existing {@link SecurityObject}s to the Role.
*
* @param grants A Set of {@link SecurityObject}s to be added to the Role
* @throws IllegalArgumentException if grants is {@literal null}
*/
public void addGrants(Set<SecurityObject> grants) {
Assert.notNull(grants, "Set of Grants must not be null");
getGrants().addAll(grants);
}
/**
* Add an existing {@link SecurityObject} to the Role.
*
* @param grant The {@link SecurityObject} to be added to the Role
* @return {@literal true} if the {@link SecurityObject} was successfully removed from the Set of {@link SecurityObject}s, otherwise
* {@literal false}
* @throws IllegalArgumentException if grant is {@literal null}
*/
public boolean removeGrant(SecurityObject grant) {
Assert.notNull(grant, "Grant to remove must not be null");
return grants.remove(grant);
}
/**
* Add an existing {@link SecurityObject} to the Role.
*
* @param grants A list of {@link SecurityObject}s to be removed from the Role
* @return {@literal true} if the {@link SecurityObject} was successfully removed from the Set of {@link SecurityObject}s, otherwise
* {@literal false}
* @throws IllegalArgumentException if {@code grants} is {@literal null}
*/
public boolean removeGrants(List<? extends SecurityObject> grants) {
Assert.notNull(grants, "Grants to remove must not be null");
return this.grants.removeAll(grants);
}
/**
* Set all {@link SecurityObject}s assigned to the Role. Already existing {@link SecurityObject}s will be removed.
*
* @param grants A Set of {@link SecurityObject}s to be assigned to the Role
* @throws IllegalArgumentException if grants is {@literal null}
*/
public void setGrants(Set<SecurityObject> grants) {
Assert.notNull(grants, "Set of Grants must not be null");
this.grants = grants;
}
/**
* {@inheritDoc}
* <p>
* Delegates to the superclass and uses the hashCode of the String ROLE for calculation.
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = super.hashCode();
result = prime * result + "ROLE".hashCode();
return result;
}
/**
* {@inheritDoc}
* <p>
* Does not delegate to the {@link SecurityObject#equals(Object)} and uses the name for comparison.
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Role)) {
return false;
}
var other = (Role) obj;
if (this.getName() == null) {
if (other.getName() != null) {
return false;
}
} else if (!this.getName().equals(other.getName())) {
return false;
}
return true;
}
}