How to configure oneToMany Mapping in User -Role-Permission - spring-boot

I have a spring boot app(Spring boot 2) with spring data jpa.i have 3 MYSQL tables to store user information ,roles and permissions
User will contains basic user details like username ,password firstname,lastname.
Role represent the user roles like Admin ,User,Staff,test (user can have many roles)
Permission has 3 possibilities Read,Write,Customize(each role has many permissions)
UserRole - joint table for User and Roles
RolePermission - joint table for roles and permissions
Am looking for Spring boot service , which will return false if the user is not validated against the User table ,if the user is validated successfully then the response should contains the user roles and permissions .
I was able to build the sample rest service but failed to setup the below
How to configure the entity classes for the oneToMany mapping in this requirement
What would be the corresponding query in the repository interface
Please see the entity tables
#Entity
#Table( name = "TURBINE_USER" )
public class PortalUser {
public PortalUser() {
}
public PortalUser(long userID ,String userName , String password , String firstName, String lastName, String email) {
this.userID = userID;
this.userName = userName;
this.password = password;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
#Id
#Column(name="USER_ID",unique=true)
private long userID;
#NotNull
#Column(name="LOGIN_NAME",unique=true)
private String userName;
#NotNull
#Column(name="PASSWORD_VALUE")
private String password;
#NotNull
#ColumnDefault("")
#Column(name="FIRST_NAME")
private String firstName;
#NotNull
#ColumnDefault("")
#Column(name="LAST_NAME")
private String lastName;
#Column(name="EMAIL")
private String email;
#Column(name="CONFIRM_VALUE")
private String confirmValue;
#NotNull
#Column(name="CREATED")
private Timestamp createdDt;
#NotNull
#Column(name="MODIFIED")
private Timestamp modified;
#NotNull
#Column(name="LAST_LOGIN")
private Timestamp lastLogin;
#Column(name="DISABLED")
private char disabled;
#Column(name="OBJECTDATA")
private byte[] objectData;
#NotNull
#Column(name="PASSWORD_CHANGED")
private Timestamp passwordChanged;
// getters and setters
}
#Entity
#Table(name = "TURBINE_ROLE")
public class Role {
#Id
#Column(name= "ROLE_ID",unique = true)
private long roleId;
#Column(name= "ROLE_NAME")
private String roleName;
}
#Entity
#Table(name ="TURBINE_PERMISSION")
public class Permission {
#Id
#Column(name= "PERMISSION_ID")
private long permissionId;
#Column(name= "PERMISSION_NAME")
private String name;
}
#Entity
#Table(name= "TURBINE_USER_GROUP_ROLE")
public class UserRoles {
#Column(name="USER_ID")
private PortalUser user;
#Column(name="ROLE_ID")
private Role roles;
}
#Entity
#Table(name="TURBINE_ROLE_PERMISSION")
public class RolePermission {
#Column(name= "ROLE_ID")
private Role roleId;
#Column(name= "PERMISSIONID")
private Permission permissionId;
}

It is ManyToMany relationship between User and Role, Role and Permission entities. You should not define Join tables as entities, and they are generated/managed by JPA implementation.
For example, ManyToMany annotation for User and Role entities:
public class PortalUser {
...
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "User_Role",
joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "role_id")})
private Set<Role> roles;
}
Tested with CMobileCom JPA.
Disclaimer: I am a developer of CMobileCom JPA, a light weight JPA implementation for Java and Android.

One of the simple way to define the relation among User-Role-Permission is as bellow - Define entities class for Role and Permission and declare the many to many relation inside User entity, Which auto created the two link table user_role and user_permission.
1. Role Entity Class
#Entity
#Table(name = "ROLE")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
.
.
.
}
2. Permission Entity Class
#Entity
#Table(name = "PERMISSION")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Permission {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
.
.
.
}
3. User Entity Class
#Entity
#Table(name = "USER")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
.
.
.
// Relation with role
#ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
#JoinTable(name="USER_ROLE",
joinColumns = {#JoinColumn (name="USER_ID", referencedColumnName="id")},
inverseJoinColumns = {#JoinColumn(name="ROLE_ID", referencedColumnName="id")}
)
private List<Role> roles;
// Relation with permission
#ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
#JoinTable(name="USER_PERMISSION",
joinColumns = {#JoinColumn (name="USER_ID", referencedColumnName="id")},
inverseJoinColumns = {#JoinColumn(name="PERMISSION_ID", referencedColumnName="id")}
)
private List<Permission> permissions;
}

Related

Spring JPA Unable To Find Composite Foreign Key Target Column (Non-PK)

User.java
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_role_id", referencedColumnName = "id")
private UserRole userRole;
}
UserRole.java
#Data
#Entity
#Table(name = "user_roles")
public class UserRole implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
}
Client.java
#Data
#Entity
#Table(name = "clients")
public class Client implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumns({ #JoinColumn(name = "user_id", referencedColumnName = "id"),
#JoinColumn(name = "user_role_id", referencedColumnName = "user_role_id") })
private User user;
}
Error
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.MappingException: Unable to find column with logical name: user_role_id in users
In RDBMS, users.(id, user_role_id) is unique so clients table can refer to that.
Last time, I was using insertable = false, updatable = false on user_role_id, but when I want to add records of new client, I always need to add user_role_id manually user.setUserRoleId(userRole.getId()) after user.setUserRole(userRole) and I think that is bad practice of ORM (it should be added automatically when I set user.setUserRole(userRole))
#Column(name = "user_role_id", insertable = false, updatable = false)
private Integer userRoleId;
What should I do so the relation can be mapped in Spring JPA? and what is the best practice?
In other words, this is also mean how to reference to foreign key generated logical name column?
OK! Please try following configuration:
Below is a important code part and under this link you may find repository with working example
UserRole.java
#Data
#Entity
#Table(name = "user_roles")
public class UserRole implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "role_id")
private Integer roleId;
}
User.java
#Data
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Integer userId;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "user_role_id", referencedColumnName = "role_id")
private UserRole userRole;
}
Client.java
#Data
#Entity
#Table(name = "clients")
public class Client implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "client_id")
private Integer clientId;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumns(
value = {
#JoinColumn(name = "client_role_id", referencedColumnName = "user_role_id"),
#JoinColumn(name = "client_user_id", referencedColumnName = "user_id"),
}
,
foreignKey = #ForeignKey(
name = "FK_user_with_role",
foreignKeyDefinition = "FOREIGN KEY (client_user_id, client_role_id)\n" +
" REFERENCES users \n" +
" (user_id, user_role_id) \n" +
" ON UPDATE CASCADE\n" +
" ON DELETE CASCADE")
)
private User user;
}
Please note that beside adding a foreignKey in the Client implementation, you MUST keep the sequence of #JoinColum annotations.. I don't know what is the reason behind, but if you flip those lines you'll still get your error as it was before :)
EDIT: I've added another answer which fits best in my opinion. I'm leaving this one as well to see the other steps I tried.
Though the solution is not elegant and not using JPA as requested. Just in case anything in here would be helpful
If I understand the main issue correctly - you want to bind Client entity with Role entity via User entity, by first setting User's Role and then transfer that "property" by using only UserId instead setting additionally RoleId while creating Client.
Basically after playing for a while with your model I think the main issue is to assign data to each other within a #Transactional methods. That seems to be caused ba Lazy fetch strategy.
My proposal for solution that binds all your Entities according expectations differs only from yours with ommiting the RoleId JoinColumn in Clients table. I have checked that when calling a service that would have #Transactional methods, you can assign a Role to the User and User to the Client with simple user.setRole(roleEntity) followed by client.setUser(userEntity).
All the data is then consistent. No need to call further like getters and setters as you mentioned in the second part of your question. Question is if for any reason you need to have RoleId as well in your Clients Table, then this soultion would have to be enhanced by additional column?
UserRole.java
#Entity
#Table(name = "user_roles")
public class UserRole implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "role_id")
private Integer roleId;
//getters and setters and toString
}
User.java
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Integer userId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_role_id", referencedColumnName = "role_id")
private UserRole userRole;;
//getters and setters and toString;
}
Client.java
#Entity
#Table(name = "clients")
public class Client implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "client_id")
private Integer clientId;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "client_user_id", referencedColumnName = "user_id"),
})
private User user;
#Column(name = "client_role_id")
private Integer roleId;
#PrePersist
#PreUpdate
private void prePersist(){
try {
roleId = getUser().getUserRole().getRoleId();
} catch (NullPointerException e){
roleId = null;
}
}
//getters and setters and toString
}
UserService.java
#Service
public class UserService {
UserRepo userRepo;
public UserService(UserRepo userRepo) {
this.userRepo = userRepo;
}
#Transactional
public void save(User user) {
userRepo.save(user);
}
#Transactional
public User getReferenceById(int i) {
return userRepo.getReferenceById(i);
}
}
ClientService.java
#Service
public class ClientService {
private ClientRepo clientRepo;
private UserService userService;
public ClientService(ClientRepo clientRepo, UserService userService) {
this.clientRepo = clientRepo;
this.userService = userService;
}
#Transactional
public Client save(Client client){
return clientRepo.save(client);
}
#Transactional
public Client getReferenceById(int i) {
return clientRepo.getReferenceById(i);
}
#Transactional
public void printClient(Client client){
client = clientRepo.getReferenceById(client.getClientId());
System.out.println(client);
}
#Transactional
public void bindUserToClient(int userId, int clientId) {
Client entity = clientRepo.findById(clientId).orElseGet(Client::new);
entity.setUser(userService.getReferenceById(userId));
}
#Transactional
public void printClient(int i) {
clientRepo.findById(i).ifPresentOrElse(this::printClient, EntityNotFoundException::new);
}
}
This configuration after running this commandLineRunner:
#Configuration
public class Config {
#Bean
#Transactional
public CommandLineRunner commandLineRunner(
#Autowired UserRoleRepo roleRepo,
#Autowired UserService userService,
#Autowired ClientService clientService
) {
return args -> {
for (int i = 0; i < 5; i++) {
roleRepo.save(new UserRole());
}
for (int i = 5; i > 0; i--) {
User user = new User();
user.setUserRole(roleRepo.getReferenceById(i));
userService.save(user);
}
Client client = new Client();
client.setUser(userService.getReferenceById(2));
client = clientService.save(client);
clientService.printClient(client);
client = new Client();
client.setClientId(1);
clientService.printClient(client);
int userId = 5;
clientService.bindUserToClient(userId, 1);
clientService.printClient(1);
};
}
}
gave me correct output in the console:
Client{id=1, user=User{id=2, userRole=UserRole{id=4}}}
Client{id=1, user=User{id=2, userRole=UserRole{id=4}}}
Client{id=1, user=User{id=5, userRole=UserRole{id=1}}}
WORKAROUND
I tried to reach the goal by use of Spring JPA but could'nt.
The workaround that keeps the referential integrity was by creating a constrains through DB like below and add #PrePersist and #PreUpdate annotated method which is updating the client's roleId as intended.
create table clients
(
client_id integer not null,
client_user_id integer,
client_role_id integer,
primary key (client_id)
);
create table user_roles
(
role_id integer generated by default as identity,
primary key (role_id)
);
create table users
(
user_id integer generated by default as identity,
user_role_id integer,
primary key (user_id),
CONSTRAINT User_Role UNIQUE (user_id, user_role_id)
);
alter table users
add constraint FK_role_id foreign key (user_role_id) references user_roles (role_id);
alter table clients
add constraint FK_user_id foreign key (client_user_id, client_role_id) references users (user_id, user_role_id) on update cascade ;
Thanks to that I could for instance update userRole in user entity, and the change was reflected in the clients table as well without any further actions

OneToOne CascadeType in spring data jpa

I use OneToOne in the spring data JPA and I want to delete a record from the Address table without touching the user. But I can't.
If I remove User, in this case Address is removed, that's good.
But how can you delete an Address without touching the User?
https://github.com/myTestPercon/TestCascade
User.Java
#Entity
#Table(name = "user", schema = "testCascade")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
private Address address;
// Getter and Setter ...
}
Address.java
#Entity
#Table(name = "address", schema = "testCascade")
public class Address implements Serializable {
#Id
private Long id;
#Column(name = "city")
private String city;
#OneToOne
#MapsId
#JoinColumn(name = "id")
private User user;
// Getter and Setter ...
}
DeleteController.java
#Controller
public class DeleteController {
#Autowired
ServiceJpa serviceJpa;
#GetMapping(value = "/deleteAddressById")
public String deleteAddressById () {
serviceJpa.deleteAddressById(4L);
return "redirect:/home";
}
}
You got your mapping wrong thats all is the problem .
try the below and see
User.java
#Entity
#Table(name = "user", schema = "testCascade")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="foriegn key column in user table for address example.. address_id")
private Address address;
// Getter and Setter ...
}
Address.java
#Entity
#Table(name = "address", schema = "testCascade")
public class Address implements Serializable {
#Id
private Long id;
#Column(name = "city")
private String city;
//name of the address variable in your user class
#OneToOne(mappedBy="address",
cascade={CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.REFRESH})
private User user;
// Getter and Setter ...
}
In order to solve this problem, you need to read the hibernate Documentation Hibernate Example 162, Example 163, Example 164.
And also I recommend to look at this is Using #PrimaryKeyJoinColumn annotation in spring data jpa
This helped me in solving this problem.
And also you need to specify the parameter orphanRemoval = true
User.java
#Entity(name = "User")
#Table(name = "user", schema = "testother")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private Address address;
public void addAddress(Address address) {
address.setUser( this );
this.address = address;
}
public void removeAddress() {
if ( address != null ) {
address.setUser( null );
this.address = null;
}
}
// Getter and Setter
}
Address.java
#Entity(name = "Address")
#Table(name = "address", schema = "testother")
public class Address implements Serializable {
#Id
private Long id;
#Column(name = "city")
private String city;
#OneToOne
#MapsId
#JoinColumn(name = "id")
private User user;
// Getter and Setter
}
DeleteController .java
#Controller
public class DeleteController {
#Autowired
ServiceJpa serviceJpa;
#GetMapping(value = "/deleteUser")
public String deleteUser () {
User user = serviceJpa.findUserById(2L).get();
user.removeAddress();
serviceJpa.saveUser(user);
return "/deleteUser";
}
}
Or make a custom SQL query.
#Repository
public interface DeleteAddress extends JpaRepository<Address, Long> {
#Modifying
#Query("delete from Address b where b.id=:id")
void deleteBooks(#Param("id") Long id);
}
public class Address {
#Id
private Long id;
#MapsId
#JoinColumn(name = "id")
private User user;
}
Rename #JoinColumn(name = "id") to #JoinColumn(name = "user_id")
You can't say that the column that will point to user will be the id of the Address

JPA Fetch Types

I have two model classed called users and authorities. I used fetch type EAGER so that all the data even the ones from the authorities tables would be fetched as they are mapped to each other, But for some reason it is not fetching the details from the authorities table.
#Getter
#Setter
#Entity
#Table
public class Users {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(unique = true)
private String email;
private String firstname;
private String lastname;
private String password;
#OneToMany(mappedBy = "users",
cascade = CascadeType.PERSIST,
fetch = FetchType.EAGER,
targetEntity = Authorities.class)
private Set<Authorities> authorities;
}
#Getter
#Setter
#Entity
#Table
public class Authorities {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String role;
#ManyToOne(targetEntity = Users.class,
cascade = CascadeType.PERSIST,
fetch = FetchType.EAGER)
#JoinColumn(name = "authorities")
private Users users;
}
#Repository
public interface UserRepository extends JpaRepository<Users, Integer> {
Optional<Users> findByEmail(String email);
}
This method findByEmail is not fetching all the details from the database. It is only fetching the data from the users table but not from the authorities table even though i am using fetch type EAGER.
I think that Spring Data ignores fetch types and fetch mode. You can use Entity Graphs. See documentation
#Getter
#Setter
#Entity
#Table
#NamedEntityGraph(name = "authorities.detail", attributeNodes = NamedAttributeNode("authorities"))
public class Users {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(unique = true)
private String email;
private String firstname;
private String lastname;
private String password;
#OneToMany(mappedBy = "users",
cascade = CascadeType.PERSIST,
fetch = FetchType.EAGER,
targetEntity = Authorities.class)
private Set<Authorities> authorities;
}

Why I can't delete data in cascade way?

The problem is when I want to delete user I'm getting error in Spring Boot like that:
java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (32506632_pam.badge, CONSTRAINT FK4aamfo6o0h5ejqjn40fv40jdw FOREIGN KEY (user_id) REFERENCES user (id))
I'm guessing that I need to delete data in cascade way. So I've placed CascadeType.REMOVE value to #OneToOne annotation like that, but it doesn't work:
badge entity
#Entity
#Data
#Table(name = "badge")
#AllArgsConstructor
#NoArgsConstructor
public class Badge {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonManagedReference
#ManyToMany(mappedBy = "badges", fetch = FetchType.LAZY)
private List<Reader> readers;
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval=true)
#JoinColumn(name = "user_id")
private User user;
private String number;
#Lob
#Basic(fetch = FetchType.LAZY)
private byte[] photo;
}
user entity
#Entity
#Data
#Table(name = "user")
#AllArgsConstructor
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String lastname;
private String pesel;
private String email;
private String telephone;
private Integer age;
private String gender;
}
reader entity
#Entity
#Data
#Table(name = "reader")
#AllArgsConstructor
#NoArgsConstructor
public class Reader {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonBackReference
#ManyToMany(fetch = FetchType.LAZY)
private List<Badge> badges;
private String department;
private String room;
private Boolean status;
}
Class which loads initial data
#Component
public class DataLoader implements ApplicationRunner {
#Autowired
private UserService userService;
#Autowired
private BadgeService badgeService;
#Autowired
private ReaderService readerService;
#Override
public void run(ApplicationArguments args) throws Exception {
User user1 = new User(null, "Jan", "Kowal", "11111111111", "jan#kowal.pl", "+48111111111", new Integer(23), "male");
userService.saveUser(user1);
Reader reader1 = new Reader(null, null, "Warehouse", "207A", new Boolean("true"));
Badge badge1 = new Badge(null, Arrays.asList(reader1), user1, "738604289120", null);
badgeService.saveBadge(badge1);
reader1.setBadges(Arrays.asList(badge1));
readerService.saveReader(reader1);
}
}
Endpoint for deleting user - it uses repository which extends CrudRepository and uses default delete behavior.
#DeleteMapping("/deleteUserById/{id}")
private void deleteUserById(#PathVariable Long id) {
userService.deleteUserById(id);
}
Database structure in phpmyadmin
My goal is to delete user and associated badge with him, then to delete row in reader_badges table.

how to insert data in one to many relationship in spring jpa

I am new in Spring. I want to insert the data into the AUDIT table. There is a one-two-many relationship between USER and AUDIT tables.
For every login and logout data should be inserted into the AUDIT table. How can I do that?
The user entity:
#Table(name="USER")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="USERID")
private Long id;
private String username;
private String email;
private Long phone;
private String password;
private int OTP;
private boolean activation_flag;
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name="USERID")
private Set<Audit> audit;
#ManyToOne
#JoinColumn(name="ADDRESSID")
private Address address;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "USER_AUTHORITY",
joinColumns = {#JoinColumn(name = "USER_ID", referencedColumnName = "USERID")},
inverseJoinColumns = {#JoinColumn(name = "AUTHORITY_ID", referencedColumnName = "ID")})
private List<Authority> authorities;
The audit entity:
#Entity
#Table(name="AUDIT")
public class Audit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="AUDITID")
private long auditid;
#Column(name="GEOLOCATION")
private String geolocation;
#Column(name="ACTION")
private String action;
#Column(name="DATETIME")
private Date datetime;
#Column(name="DEVICEID")
private long deviceid;
#Column(name="ACTIONSUCCESS_FAIL")
private boolean actionsuccess_fail;
#Column(name="JWT_TOKEN")
#Type(type="text")
private String JWT_token;
#ManyToOne
private User user;
The audit repository:
public interface AuditRepository extends JpaRepository<Audit, Long> {
}
The user repository:
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
User findByEmail(String email);
User findByPhone(Long phone);
User findById(Long id);
}
Add the audit to the user you want the audit associated with, then save.

Resources