Add a new Lock Table that will prevent two or more users to edit simultaneously a single DMP or Dataset (ref #240)
This commit is contained in:
parent
ccea83b4d4
commit
b62c0f7ff5
|
@ -0,0 +1,38 @@
|
|||
package eu.eudat.data.dao.criteria;
|
||||
|
||||
import eu.eudat.data.entities.Lock;
|
||||
import eu.eudat.data.entities.UserInfo;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
public class LockCriteria extends Criteria<Lock> {
|
||||
|
||||
private UUID target;
|
||||
private UserInfo lockedBy;
|
||||
private Date touchedAt;
|
||||
|
||||
public UUID getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public void setTarget(UUID target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public UserInfo getLockedBy() {
|
||||
return lockedBy;
|
||||
}
|
||||
|
||||
public void setLockedBy(UserInfo lockedBy) {
|
||||
this.lockedBy = lockedBy;
|
||||
}
|
||||
|
||||
public Date getTouchedAt() {
|
||||
return touchedAt;
|
||||
}
|
||||
|
||||
public void setTouchedAt(Date touchedAt) {
|
||||
this.touchedAt = touchedAt;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package eu.eudat.data.dao.entities;
|
||||
|
||||
import eu.eudat.data.dao.DatabaseAccessLayer;
|
||||
import eu.eudat.data.dao.criteria.LockCriteria;
|
||||
import eu.eudat.data.entities.Lock;
|
||||
import eu.eudat.queryable.QueryableList;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface LockDao extends DatabaseAccessLayer<Lock, UUID> {
|
||||
|
||||
QueryableList<Lock> getWithCriteria(LockCriteria criteria);
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package eu.eudat.data.dao.entities;
|
||||
|
||||
import eu.eudat.data.dao.DatabaseAccess;
|
||||
import eu.eudat.data.dao.criteria.LockCriteria;
|
||||
import eu.eudat.data.dao.databaselayer.service.DatabaseService;
|
||||
import eu.eudat.data.entities.Lock;
|
||||
import eu.eudat.queryable.QueryableList;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Service("LockDao")
|
||||
public class LockDaoImpl extends DatabaseAccess<Lock> implements LockDao {
|
||||
|
||||
@Autowired
|
||||
public LockDaoImpl(DatabaseService<Lock> databaseService) {
|
||||
super(databaseService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryableList<Lock> getWithCriteria(LockCriteria criteria) {
|
||||
QueryableList<Lock> query = this.getDatabaseService().getQueryable(Lock.class);
|
||||
if (criteria.getTouchedAt() != null)
|
||||
query.where((builder, root) -> builder.equal(root.get("touchedAt"), criteria.getTouchedAt()));
|
||||
if (criteria.getLockedBy() != null)
|
||||
query.where(((builder, root) -> builder.equal(root.get("lockedBy"), criteria.getLockedBy())));
|
||||
if (criteria.getTarget() != null)
|
||||
query.where(((builder, root) -> builder.equal(root.get("target"), criteria.getTarget())));
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lock createOrUpdate(Lock item) {
|
||||
return this.getDatabaseService().createOrUpdate(item, Lock.class);
|
||||
}
|
||||
|
||||
@Async
|
||||
@Override
|
||||
public CompletableFuture<Lock> createOrUpdateAsync(Lock item) {
|
||||
return CompletableFuture.supplyAsync(() -> this.createOrUpdate(item));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lock find(UUID id) {
|
||||
return this.getDatabaseService().getQueryable(Lock.class).where(((builder, root) -> builder.equal(root.get("id"), id))).getSingle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lock find(UUID id, String hint) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Lock item) {
|
||||
this.getDatabaseService().delete(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryableList<Lock> asQueryable() {
|
||||
return this.getDatabaseService().getQueryable(Lock.class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package eu.eudat.data.entities;
|
||||
|
||||
import eu.eudat.data.converters.DateToUTCConverter;
|
||||
import eu.eudat.data.entities.helpers.EntityBinder;
|
||||
import eu.eudat.queryable.queryableentity.DataEntity;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "\"Lock\"")
|
||||
public class Lock implements DataEntity<Lock, UUID> {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
@GenericGenerator(name = "uuid2", strategy = "uuid2")
|
||||
@Column(name = "id", updatable = false, nullable = false, columnDefinition = "BINARY(16)")
|
||||
private UUID id;
|
||||
|
||||
@Column(name = "\"Target\"", nullable = false)
|
||||
private UUID target;
|
||||
|
||||
@ManyToOne(fetch = FetchType.EAGER)
|
||||
@JoinColumn(name = "\"LockedBy\"", nullable = false)
|
||||
private UserInfo lockedBy;
|
||||
|
||||
@Column(name = "\"LockedAt\"")
|
||||
@Convert(converter = DateToUTCConverter.class)
|
||||
private Date lockedAt = new Date();
|
||||
|
||||
@Column(name = "\"TouchedAt\"")
|
||||
@Convert(converter = DateToUTCConverter.class)
|
||||
private Date touchedAt = null;
|
||||
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public UUID getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public void setTarget(UUID target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public UserInfo getLockedBy() {
|
||||
return lockedBy;
|
||||
}
|
||||
|
||||
public void setLockedBy(UserInfo lockedBy) {
|
||||
this.lockedBy = lockedBy;
|
||||
}
|
||||
|
||||
public Date getLockedAt() {
|
||||
return lockedAt;
|
||||
}
|
||||
|
||||
public void setLockedAt(Date lockedAt) {
|
||||
this.lockedAt = lockedAt;
|
||||
}
|
||||
|
||||
public Date getTouchedAt() {
|
||||
return touchedAt;
|
||||
}
|
||||
|
||||
public void setTouchedAt(Date touchedAt) {
|
||||
this.touchedAt = touchedAt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Lock entity) {
|
||||
this.touchedAt = entity.touchedAt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getKeys() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lock buildFromTuple(List<Tuple> tuple, List<String> fields, String base) {
|
||||
String currentBase = base.isEmpty() ? "" : base + ".";
|
||||
if (fields.contains(currentBase + "id")) this.id = EntityBinder.fromTuple(tuple, currentBase + "id");
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -69,6 +69,9 @@ public class UserInfo implements DataEntity<UserInfo, UUID> {
|
|||
@OneToMany(mappedBy = "userInfo", fetch = FetchType.LAZY)
|
||||
private Set<UserRole> userRoles = new HashSet<>();
|
||||
|
||||
@OneToMany(mappedBy = "lockedBy", fetch = FetchType.LAZY)
|
||||
private Set<Lock> locks = new HashSet<>();
|
||||
|
||||
public Set<DMP> getDmps() {
|
||||
return dmps;
|
||||
}
|
||||
|
@ -165,6 +168,14 @@ public class UserInfo implements DataEntity<UserInfo, UUID> {
|
|||
this.userRoles = userRoles;
|
||||
}
|
||||
|
||||
public Set<Lock> getLocks() {
|
||||
return locks;
|
||||
}
|
||||
|
||||
public void setLocks(Set<Lock> locks) {
|
||||
this.locks = locks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(UserInfo entity) {
|
||||
this.name = entity.getName();
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package eu.eudat.data.query.items.item.lock;
|
||||
|
||||
import eu.eudat.data.dao.criteria.LockCriteria;
|
||||
import eu.eudat.data.entities.Lock;
|
||||
import eu.eudat.data.query.definition.Query;
|
||||
import eu.eudat.queryable.QueryableList;
|
||||
|
||||
public class LockCriteriaRequest extends Query<LockCriteria, Lock> {
|
||||
@Override
|
||||
public QueryableList<Lock> applyCriteria() {
|
||||
QueryableList<Lock> query = this.getQuery();
|
||||
if (this.getCriteria().getTouchedAt() != null)
|
||||
query.where((builder, root) -> builder.equal(root.get("touchedAt"), this.getCriteria().getTouchedAt()));
|
||||
if (this.getCriteria().getLockedBy() != null)
|
||||
query.where(((builder, root) -> builder.equal(root.get("lockedBy"), this.getCriteria().getLockedBy())));
|
||||
if (this.getCriteria().getTarget() != null)
|
||||
query.where(((builder, root) -> builder.equal(root.get("target"), this.getCriteria().getTarget())));
|
||||
return query;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package eu.eudat.data.query.items.table.lock;
|
||||
|
||||
import eu.eudat.data.dao.criteria.LockCriteria;
|
||||
import eu.eudat.data.entities.Lock;
|
||||
import eu.eudat.data.query.PaginationService;
|
||||
import eu.eudat.data.query.definition.Query;
|
||||
import eu.eudat.data.query.definition.TableQuery;
|
||||
import eu.eudat.queryable.QueryableList;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class LockTableRequest extends TableQuery<LockCriteria, Lock, UUID> {
|
||||
@Override
|
||||
public QueryableList<Lock> applyCriteria() {
|
||||
QueryableList<Lock> query = this.getQuery();
|
||||
if (this.getCriteria().getTouchedAt() != null)
|
||||
query.where((builder, root) -> builder.equal(root.get("touchedAt"), this.getCriteria().getTouchedAt()));
|
||||
if (this.getCriteria().getLockedBy() != null)
|
||||
query.where(((builder, root) -> builder.equal(root.get("lockedBy"), this.getCriteria().getLockedBy())));
|
||||
if (this.getCriteria().getTarget() != null)
|
||||
query.where(((builder, root) -> builder.equal(root.get("target"), this.getCriteria().getTarget())));
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryableList<Lock> applyPaging(QueryableList<Lock> items) {
|
||||
return PaginationService.applyPaging(items, this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package eu.eudat.query;
|
||||
|
||||
import eu.eudat.data.dao.DatabaseAccessLayer;
|
||||
import eu.eudat.data.entities.Lock;
|
||||
import eu.eudat.data.entities.UserInfo;
|
||||
import eu.eudat.queryable.QueryableList;
|
||||
import eu.eudat.queryable.types.FieldSelectionType;
|
||||
import eu.eudat.queryable.types.SelectionField;
|
||||
|
||||
import javax.persistence.criteria.Subquery;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class LockQuery extends Query<Lock, UUID> {
|
||||
|
||||
private UUID id;
|
||||
private UUID target;
|
||||
private UserQuery userQuery;
|
||||
private Date touchedAt;
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public UUID getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public void setTarget(UUID target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public UserQuery getUserQuery() {
|
||||
return userQuery;
|
||||
}
|
||||
|
||||
public void setUserQuery(UserQuery userQuery) {
|
||||
this.userQuery = userQuery;
|
||||
}
|
||||
|
||||
public Date getTouchedAt() {
|
||||
return touchedAt;
|
||||
}
|
||||
|
||||
public void setTouchedAt(Date touchedAt) {
|
||||
this.touchedAt = touchedAt;
|
||||
}
|
||||
|
||||
public LockQuery(DatabaseAccessLayer<Lock, UUID> databaseAccessLayer, List<String> selectionFields) {
|
||||
super(databaseAccessLayer, selectionFields);
|
||||
}
|
||||
|
||||
public LockQuery(DatabaseAccessLayer<Lock, UUID> databaseAccessLayer) {
|
||||
super(databaseAccessLayer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryableList<Lock> getQuery() {
|
||||
QueryableList<Lock> query = this.databaseAccessLayer.asQueryable();
|
||||
if (this.id != null) {
|
||||
query.where((builder, root) -> builder.equal(root.get("id"), this.id));
|
||||
}
|
||||
if (this.target != null) {
|
||||
query.where(((builder, root) -> builder.equal(root.get("target"), this.target)));
|
||||
}
|
||||
if (this.userQuery != null) {
|
||||
Subquery<UserInfo> userSubQuery = this.userQuery.getQuery().query(Arrays.asList(new SelectionField(FieldSelectionType.FIELD, "id")));
|
||||
query.where((builder, root) -> root.get("lockedBy").get("id").in(userSubQuery));
|
||||
}
|
||||
return query;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package eu.eudat.controllers;
|
||||
|
||||
import com.sun.org.apache.xpath.internal.operations.Bool;
|
||||
import eu.eudat.logic.managers.LockManager;
|
||||
import eu.eudat.models.data.dmp.DataManagementPlan;
|
||||
import eu.eudat.models.data.helpers.responses.ResponseItem;
|
||||
import eu.eudat.models.data.lock.Lock;
|
||||
import eu.eudat.models.data.security.Principal;
|
||||
import eu.eudat.types.ApiMessageCode;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin
|
||||
@RequestMapping(value = {"/api/lock/"})
|
||||
public class LockController {
|
||||
|
||||
private LockManager lockManager;
|
||||
|
||||
@Autowired
|
||||
public LockController(LockManager lockManager) {
|
||||
this.lockManager = lockManager;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@RequestMapping(method = RequestMethod.GET, path = "target/status/{id}")
|
||||
public @ResponseBody ResponseEntity<ResponseItem<Boolean>> getLocked(@PathVariable String id, Principal principal) throws Exception {
|
||||
boolean locked = this.lockManager.isLocked(id, principal);
|
||||
return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem<Boolean>().status(ApiMessageCode.SUCCESS_MESSAGE).message("locked").payload(locked));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@RequestMapping(method = RequestMethod.DELETE, path = "target/unlock/{id}")
|
||||
public @ResponseBody ResponseEntity<ResponseItem<String>> unlock(@PathVariable String id, Principal principal) throws Exception {
|
||||
this.lockManager.unlock(id, principal);
|
||||
return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem<String>().status(ApiMessageCode.SUCCESS_MESSAGE).message("Created").payload("Lock Removed"));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@RequestMapping(method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
|
||||
public @ResponseBody ResponseEntity<ResponseItem<UUID>> createOrUpdate(@RequestBody Lock lock, Principal principal) throws Exception {
|
||||
eu.eudat.data.entities.Lock result = this.lockManager.createOrUpdate(lock, principal);
|
||||
return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem<UUID>().status(ApiMessageCode.SUCCESS_MESSAGE).message("Created").payload(result.getId()));
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "target/{id}")
|
||||
public @ResponseBody ResponseEntity<ResponseItem<Lock>> getSingle(@PathVariable String id, Principal principal) throws Exception {
|
||||
Lock lock = this.lockManager.getFromTarget(id, principal);
|
||||
return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem<Lock>().status(ApiMessageCode.NO_MESSAGE).payload(lock));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
package eu.eudat.logic.managers;
|
||||
|
||||
import eu.eudat.data.dao.criteria.LockCriteria;
|
||||
import eu.eudat.logic.services.ApiContext;
|
||||
import eu.eudat.models.data.lock.Lock;
|
||||
import eu.eudat.models.data.security.Principal;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.persistence.NoResultException;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
public class LockManager {
|
||||
|
||||
private ApiContext apiContext;
|
||||
private Environment environment;
|
||||
|
||||
@Autowired
|
||||
public LockManager(ApiContext apiContext, Environment environment) {
|
||||
this.apiContext = apiContext;
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
public eu.eudat.data.entities.Lock createOrUpdate(Lock lock, Principal principal) throws Exception {
|
||||
if (lock.getId() != null) {
|
||||
try {
|
||||
eu.eudat.data.entities.Lock entity = this.apiContext.getOperationsContext().getDatabaseRepository().getLockDao().find(lock.getId());
|
||||
if (entity != null) {
|
||||
if (!entity.getLockedBy().getId().equals(principal.getId())) {
|
||||
throw new Exception("Is not locked by that user");
|
||||
}
|
||||
}
|
||||
}catch(NoResultException e) {
|
||||
return new eu.eudat.data.entities.Lock();
|
||||
}
|
||||
}
|
||||
eu.eudat.data.entities.Lock newLock = lock.toDataModel();
|
||||
newLock = this.apiContext.getOperationsContext().getDatabaseRepository().getLockDao().createOrUpdate(newLock);
|
||||
|
||||
return newLock;
|
||||
}
|
||||
|
||||
public boolean isLocked(String targetId, Principal principal) throws Exception {
|
||||
LockCriteria criteria = new LockCriteria();
|
||||
criteria.setTarget(UUID.fromString(targetId));
|
||||
Long availableLocks = this.apiContext.getOperationsContext().getDatabaseRepository().getLockDao().getWithCriteria(criteria).count();
|
||||
if (availableLocks > 0) {
|
||||
eu.eudat.data.entities.Lock lock = this.apiContext.getOperationsContext().getDatabaseRepository().getLockDao().getWithCriteria(criteria).getSingle();
|
||||
if (lock.getLockedBy().getId().equals(principal.getId())) {
|
||||
lock.setTouchedAt(new Date());
|
||||
this.createOrUpdate(new Lock().fromDataModel(lock), principal);
|
||||
return false;
|
||||
}
|
||||
if (new Date().getTime() - lock.getTouchedAt().getTime() > environment.getProperty("database.lock-fail-interval", Integer.class)) {
|
||||
this.forceUnlock(targetId);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private void forceUnlock(String targetId) throws Exception {
|
||||
LockCriteria criteria = new LockCriteria();
|
||||
criteria.setTarget(UUID.fromString(targetId));
|
||||
Long availableLocks = this.apiContext.getOperationsContext().getDatabaseRepository().getLockDao().getWithCriteria(criteria).count();
|
||||
if (availableLocks > 0) {
|
||||
eu.eudat.data.entities.Lock lock = this.apiContext.getOperationsContext().getDatabaseRepository().getLockDao().getWithCriteria(criteria).getSingle();
|
||||
this.apiContext.getOperationsContext().getDatabaseRepository().getLockDao().delete(lock);
|
||||
}
|
||||
}
|
||||
|
||||
public void unlock(String targetId, Principal principal) throws Exception {
|
||||
LockCriteria criteria = new LockCriteria();
|
||||
criteria.setTarget(UUID.fromString(targetId));
|
||||
Long availableLocks = this.apiContext.getOperationsContext().getDatabaseRepository().getLockDao().getWithCriteria(criteria).count();
|
||||
if (availableLocks > 0) {
|
||||
eu.eudat.data.entities.Lock lock = this.apiContext.getOperationsContext().getDatabaseRepository().getLockDao().getWithCriteria(criteria).getSingle();
|
||||
if (!lock.getLockedBy().getId().equals(principal.getId())) {
|
||||
throw new Exception("Only the user who created that lock can delete it");
|
||||
}
|
||||
this.apiContext.getOperationsContext().getDatabaseRepository().getLockDao().delete(lock);
|
||||
}
|
||||
}
|
||||
|
||||
public Lock getFromTarget(String targetId, Principal principal) throws Exception {
|
||||
LockCriteria criteria = new LockCriteria();
|
||||
criteria.setTarget(UUID.fromString(targetId));
|
||||
Long availableLocks = this.apiContext.getOperationsContext().getDatabaseRepository().getLockDao().getWithCriteria(criteria).count();
|
||||
if (availableLocks > 0) {
|
||||
eu.eudat.data.entities.Lock lock = this.apiContext.getOperationsContext().getDatabaseRepository().getLockDao().getWithCriteria(criteria).getSingle();
|
||||
if (!lock.getLockedBy().getId().equals(principal.getId())) {
|
||||
throw new Exception("Only the user who created that lock can access it");
|
||||
}
|
||||
return new Lock().fromDataModel(lock);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -52,5 +52,7 @@ public interface DatabaseRepository {
|
|||
|
||||
FunderDao getFunderDao();
|
||||
|
||||
LockDao getLockDao();
|
||||
|
||||
<T> void detachEntity(T entity);
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ public class DatabaseRepositoryImpl implements DatabaseRepository {
|
|||
private LoginConfirmationEmailDao loginConfirmationEmailDao;
|
||||
private ProjectDao projectDao;
|
||||
private FunderDao funderDao;
|
||||
private LockDao lockDao;
|
||||
|
||||
private EntityManager entityManager;
|
||||
|
||||
|
@ -273,6 +274,16 @@ public class DatabaseRepositoryImpl implements DatabaseRepository {
|
|||
this.funderDao = funderDao;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setLockDao(LockDao lockDao) {
|
||||
this.lockDao = lockDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LockDao getLockDao() {
|
||||
return lockDao;
|
||||
}
|
||||
|
||||
public <T> void detachEntity(T entity) {
|
||||
this.entityManager.detach(entity);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package eu.eudat.models.data.lock;
|
||||
|
||||
import eu.eudat.models.DataModel;
|
||||
import eu.eudat.models.data.userinfo.UserInfo;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Lock implements DataModel<eu.eudat.data.entities.Lock, Lock> {
|
||||
private UUID id;
|
||||
private UUID target;
|
||||
private UserInfo lockedBy;
|
||||
private Date lockedAt;
|
||||
private Date touchedAt;
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public UUID getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public void setTarget(UUID target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public UserInfo getLockedBy() {
|
||||
return lockedBy;
|
||||
}
|
||||
|
||||
public void setLockedBy(UserInfo lockedBy) {
|
||||
this.lockedBy = lockedBy;
|
||||
}
|
||||
|
||||
public Date getLockedAt() {
|
||||
return lockedAt;
|
||||
}
|
||||
|
||||
public void setLockedAt(Date lockedAt) {
|
||||
this.lockedAt = lockedAt;
|
||||
}
|
||||
|
||||
public Date getTouchedAt() {
|
||||
return touchedAt;
|
||||
}
|
||||
|
||||
public void setTouchedAt(Date touchedAt) {
|
||||
this.touchedAt = touchedAt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lock fromDataModel(eu.eudat.data.entities.Lock entity) {
|
||||
this.id = entity.getId();
|
||||
this.target = entity.getTarget();
|
||||
this.lockedBy = new UserInfo().fromDataModel(entity.getLockedBy());
|
||||
this.lockedAt = entity.getLockedAt();
|
||||
this.touchedAt = entity.getTouchedAt();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public eu.eudat.data.entities.Lock toDataModel() throws Exception {
|
||||
eu.eudat.data.entities.Lock entity = new eu.eudat.data.entities.Lock();
|
||||
entity.setId(this.getId());
|
||||
entity.setTarget(this.getTarget());
|
||||
entity.setLockedAt(this.getLockedAt());
|
||||
entity.setTouchedAt(this.getTouchedAt());
|
||||
entity.setLockedBy(this.getLockedBy().toDataModel());
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHint() {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -107,8 +107,17 @@ public class UserInfo implements DataModel<eu.eudat.data.entities.UserInfo, User
|
|||
|
||||
@Override
|
||||
public eu.eudat.data.entities.UserInfo toDataModel() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
eu.eudat.data.entities.UserInfo entity = new eu.eudat.data.entities.UserInfo();
|
||||
entity.setId(this.getId());
|
||||
entity.setEmail(this.getEmail());
|
||||
entity.setName(this.getName());
|
||||
entity.setAdditionalinfo(this.getAdditionalinfo());
|
||||
entity.setAuthorization_level(this.getAuthorization_level());
|
||||
entity.setCreated(this.getCreated());
|
||||
entity.setLastloggedin(this.getLastloggedin());
|
||||
entity.setUsertype(this.getUsertype());
|
||||
entity.setVerified_email(this.getVerified_email());
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -75,3 +75,4 @@ http-logger.delay = 10
|
|||
##########################PERISTENCE##########################################
|
||||
#############GENERIC DATASOURCE CONFIGURATIONS#########
|
||||
database.driver-class-name=org.postgresql.Driver
|
||||
database.lock-fail-interval=120000
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
CREATE TABLE public."Lock" (
|
||||
id uuid NOT NULL,
|
||||
"Target" uuid NOT NULL,
|
||||
"LockedBy" uuid NOT NULL,
|
||||
"LockedAt" timestamp NOT NULL,
|
||||
"TouchedAt" timestamp
|
||||
);
|
||||
|
||||
ALTER TABLE ONLY public."Lock"
|
||||
ADD CONSTRAINT "Lock_pkey" PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY public."Lock"
|
||||
ADD CONSTRAINT "LockUserReference" FOREIGN KEY ("LockedBy") REFERENCES public."UserInfo"(id);
|
||||
|
|
@ -37,6 +37,7 @@ import { FunderService } from './services/funder/funder.service';
|
|||
import { ContactSupportService } from './services/contact-support/contact-support.service';
|
||||
import { LanguageService } from './services/language/language.service';
|
||||
import { AdminAuthGuard } from './admin-auth-guard.service';
|
||||
import { LockService } from './services/lock/lock.service';
|
||||
//
|
||||
//
|
||||
// This is shared module that provides all the services. Its imported only once on the AppModule.
|
||||
|
@ -93,7 +94,8 @@ export class CoreServiceModule {
|
|||
OrganisationService,
|
||||
EmailConfirmationService,
|
||||
ContactSupportService,
|
||||
LanguageService
|
||||
LanguageService,
|
||||
LockService
|
||||
],
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import { Guid } from '@common/types/guid';
|
||||
import { UserInfoListingModel } from '../user/user-info-listing';
|
||||
|
||||
export class LockModel {
|
||||
id: Guid;
|
||||
target: Guid;
|
||||
lockedBy: UserInfoListingModel;
|
||||
lockedAt: Date;
|
||||
touchedAt: Date;
|
||||
|
||||
constructor(targetId: string, lockedBy: any) {
|
||||
this.lockedAt = new Date();
|
||||
this.touchedAt = new Date();
|
||||
this.target = Guid.parse(targetId);
|
||||
this.lockedBy = lockedBy;
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpHeaders, HttpClient } from '@angular/common/http';
|
||||
import { BaseHttpService } from '../http/base-http.service';
|
||||
import { environment } from 'environments/environment';
|
||||
import { Observable } from 'rxjs';
|
||||
import { LockModel } from '@app/core/model/lock/lock.model';
|
||||
|
||||
@Injectable()
|
||||
export class LockService {
|
||||
|
||||
private actionUrl: string;
|
||||
private headers = new HttpHeaders();
|
||||
|
||||
constructor(private http: BaseHttpService, private httpClient: HttpClient) {
|
||||
this.actionUrl = environment.Server + 'lock/';
|
||||
}
|
||||
|
||||
checkLockStatus(id: string): Observable<boolean> {
|
||||
return this.http.get(`${this.actionUrl}target/status/${id}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
unlockTarget(id: string): Observable<any> {
|
||||
return this.http.delete(`${this.actionUrl}target/unlock/${id}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
getSingle(id: string): Observable<LockModel> {
|
||||
return this.http.get(`${this.actionUrl}target/${id}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
createOrUpdate(lock: LockModel): Observable<string> {
|
||||
return this.http.post(`${this.actionUrl}`, lock, {headers: this.headers});
|
||||
}
|
||||
}
|
|
@ -107,15 +107,16 @@
|
|||
</mat-tab-group>
|
||||
|
||||
<div class="actions">
|
||||
<mat-icon *ngIf="hasNotReversableStatus()" color="accent" class="align-self-center mr-1">info_outlined</mat-icon>
|
||||
<mat-icon *ngIf="hasNotReversableStatus() || lockStatus" color="accent" class="align-self-center mr-1">info_outlined</mat-icon>
|
||||
<div *ngIf="hasNotReversableStatus()" class="align-self-center mr-3">{{'DATASET-WIZARD.ACTIONS.INFO' | translate}}</div>
|
||||
<div *ngIf="lockStatus" class="align-self-center mr-3">{{'DATASET-WIZARD.ACTIONS.LOCK' | translate}}</div>
|
||||
<button mat-raised-button (click)="cancel()" type="button" class="cancelButton" color="primary">
|
||||
{{'DMP-EDITOR.ACTIONS.CANCEL' | translate}}
|
||||
</button>
|
||||
<button *ngIf="datasetWizardModel.status == 0 || isNew" mat-raised-button class="saveButton" color="primary" (click)="save();" type="button">{{ 'DATASET-WIZARD.ACTIONS.SAVE' | translate }}</button>
|
||||
<button *ngIf="datasetWizardModel.status == 0 || isNew" mat-raised-button class="finalizeButton" color="primary" (click)="saveFinalize();" type="button">{{ 'DATASET-WIZARD.ACTIONS.FINALIZE' | translate }}</button>
|
||||
<button *ngIf="(datasetWizardModel.status == 0 || isNew) && !lockStatus" mat-raised-button class="saveButton" color="primary" (click)="save();" type="button">{{ 'DATASET-WIZARD.ACTIONS.SAVE' | translate }}</button>
|
||||
<button *ngIf="(datasetWizardModel.status == 0 || isNew) && !lockStatus" mat-raised-button class="finalizeButton" color="primary" (click)="saveFinalize();" type="button">{{ 'DATASET-WIZARD.ACTIONS.FINALIZE' | translate }}</button>
|
||||
<div class="fill-space"></div>
|
||||
<button *ngIf="hasReversableStatus()" mat-raised-button class="reverseButton" color="primary" (click)="reverse()" type="button">{{ 'DATASET-WIZARD.ACTIONS.REVERSE' | translate }}</button>
|
||||
<button *ngIf="hasReversableStatus() && !lockStatus" mat-raised-button class="reverseButton" color="primary" (click)="reverse()" type="button">{{ 'DATASET-WIZARD.ACTIONS.REVERSE' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -31,8 +31,15 @@ import { ValidationErrorModel } from '@common/forms/validation/error-model/valid
|
|||
import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import * as FileSaver from 'file-saver';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { Observable, of as observableOf, interval } from 'rxjs';
|
||||
import { catchError, map, takeUntil } from 'rxjs/operators';
|
||||
import { LockService } from '@app/core/services/lock/lock.service';
|
||||
import { Location } from '@angular/common';
|
||||
import { LockModel } from '@app/core/model/lock/lock.model';
|
||||
import { Guid } from '@common/types/guid';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
import { AuthService } from '@app/core/services/auth/auth.service';
|
||||
import { environment } from 'environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dataset-wizard-component',
|
||||
|
@ -61,6 +68,8 @@ export class DatasetWizardComponent extends BaseComponent implements OnInit, IBr
|
|||
profileUpdateId: string;
|
||||
downloadDocumentId: string;
|
||||
isLinear = false;
|
||||
lock: LockModel;
|
||||
lockStatus: Boolean;
|
||||
|
||||
constructor(
|
||||
private datasetWizardService: DatasetWizardService,
|
||||
|
@ -73,7 +82,10 @@ export class DatasetWizardComponent extends BaseComponent implements OnInit, IBr
|
|||
public dialog: MatDialog,
|
||||
public externalSourcesConfigurationService: ExternalSourcesConfigurationService,
|
||||
private uiNotificationService: UiNotificationService,
|
||||
private formService: FormService
|
||||
private formService: FormService,
|
||||
private lockService: LockService,
|
||||
private location: Location,
|
||||
private authService: AuthService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
@ -112,6 +124,8 @@ export class DatasetWizardComponent extends BaseComponent implements OnInit, IBr
|
|||
this.datasetWizardService.getSingle(this.itemId)
|
||||
.pipe(takeUntil(this._destroyed))
|
||||
.subscribe(data => {
|
||||
this.lockService.checkLockStatus(data.id).pipe(takeUntil(this._destroyed)).subscribe(lockStatus => {
|
||||
this.lockStatus = lockStatus;
|
||||
this.datasetWizardModel = new DatasetWizardEditorModel().fromModel(data);
|
||||
this.needsUpdate();
|
||||
this.breadCrumbs = observableOf([
|
||||
|
@ -129,14 +143,23 @@ export class DatasetWizardComponent extends BaseComponent implements OnInit, IBr
|
|||
}]);
|
||||
this.formGroup = this.datasetWizardModel.buildForm();
|
||||
this.editMode = this.datasetWizardModel.status === DatasetStatus.Draft;
|
||||
if (this.datasetWizardModel.status === DatasetStatus.Finalized) {
|
||||
if (this.datasetWizardModel.status === DatasetStatus.Finalized || lockStatus) {
|
||||
this.formGroup.disable();
|
||||
this.viewOnly = true;
|
||||
}
|
||||
if (!lockStatus) {
|
||||
this.lock = new LockModel(data.id, this.authService.current());
|
||||
|
||||
this.lockService.createOrUpdate(this.lock).pipe(takeUntil(this._destroyed)).subscribe(async result => {
|
||||
this.lock.id = Guid.parse(result);
|
||||
interval(environment.lockInterval).pipe(takeUntil(this._destroyed)).subscribe(() => this.pumpLock());
|
||||
});
|
||||
}
|
||||
// if (this.viewOnly) { this.formGroup.disable(); } // For future use, to make Dataset edit like DMP.
|
||||
this.loadDatasetProfiles();
|
||||
this.registerFormListeners();
|
||||
// this.availableProfiles = this.datasetWizardModel.dmp.profiles;
|
||||
})
|
||||
},
|
||||
error => {
|
||||
this.uiNotificationService.snackBarNotification(this.language.instant('DATASET-WIZARD.MESSAGES.DATASET-NOT-FOUND'), SnackBarNotificationLevel.Error);
|
||||
|
@ -184,6 +207,8 @@ export class DatasetWizardComponent extends BaseComponent implements OnInit, IBr
|
|||
this.datasetWizardService.getSingle(this.itemId)
|
||||
.pipe(takeUntil(this._destroyed))
|
||||
.subscribe(data => {
|
||||
this.lockService.checkLockStatus(data.id).pipe(takeUntil(this._destroyed)).subscribe(lockStatus => {
|
||||
this.lockStatus = lockStatus;
|
||||
this.datasetWizardModel = new DatasetWizardEditorModel().fromModel(data);
|
||||
this.formGroup = this.datasetWizardModel.buildForm();
|
||||
this.formGroup.get('id').setValue(null);
|
||||
|
@ -216,13 +241,22 @@ export class DatasetWizardComponent extends BaseComponent implements OnInit, IBr
|
|||
});
|
||||
});
|
||||
this.editMode = this.datasetWizardModel.status === DatasetStatus.Draft;
|
||||
if (this.datasetWizardModel.status === DatasetStatus.Finalized) {
|
||||
if (this.datasetWizardModel.status === DatasetStatus.Finalized || lockStatus) {
|
||||
this.formGroup.disable();
|
||||
this.viewOnly = true;
|
||||
}
|
||||
if (!lockStatus) {
|
||||
this.lock = new LockModel(data.id, this.authService.current());
|
||||
|
||||
this.lockService.createOrUpdate(this.lock).pipe(takeUntil(this._destroyed)).subscribe(async result => {
|
||||
this.lock.id = Guid.parse(result);
|
||||
interval(environment.lockInterval).pipe(takeUntil(this._destroyed)).subscribe(() => this.pumpLock());
|
||||
});
|
||||
}
|
||||
// if (this.viewOnly) { this.formGroup.disable(); } // For future use, to make Dataset edit like DMP.
|
||||
this.loadDatasetProfiles();
|
||||
// this.availableProfiles = data.dmp.profiles;
|
||||
})
|
||||
});
|
||||
} else if (this.publicId != null) { // For Finalized -> Public Datasets
|
||||
this.isNew = false;
|
||||
|
@ -235,19 +269,30 @@ export class DatasetWizardComponent extends BaseComponent implements OnInit, IBr
|
|||
}))
|
||||
.subscribe(data => {
|
||||
if (data) {
|
||||
this.lockService.checkLockStatus(data.id).pipe(takeUntil(this._destroyed)).subscribe(lockStatus => {
|
||||
this.lockStatus = lockStatus;
|
||||
this.datasetWizardModel = new DatasetWizardEditorModel().fromModel(data);
|
||||
this.formGroup = this.datasetWizardModel.buildForm();
|
||||
this.editMode = this.datasetWizardModel.status === DatasetStatus.Draft;
|
||||
if (this.datasetWizardModel.status === DatasetStatus.Finalized) {
|
||||
if (this.datasetWizardModel.status === DatasetStatus.Finalized || lockStatus) {
|
||||
this.formGroup.disable();
|
||||
this.viewOnly = true;
|
||||
}
|
||||
if (!lockStatus) {
|
||||
this.lock = new LockModel(data.id, this.authService.current());
|
||||
|
||||
this.lockService.createOrUpdate(this.lock).pipe(takeUntil(this._destroyed)).subscribe(async result => {
|
||||
this.lock.id = Guid.parse(result);
|
||||
interval(environment.lockInterval).pipe(takeUntil(this._destroyed)).subscribe(() => this.pumpLock());
|
||||
});
|
||||
}
|
||||
this.formGroup.get('dmp').setValue(this.datasetWizardModel.dmp);
|
||||
this.loadDatasetProfiles();
|
||||
const breadcrumbs = [];
|
||||
breadcrumbs.push({ parentComponentName: null, label: this.language.instant('NAV-BAR.PUBLIC DATASETS'), url: '/explore' });
|
||||
breadcrumbs.push({ parentComponentName: null, label: this.datasetWizardModel.label, url: '/datasets/publicEdit/' + this.datasetWizardModel.id });
|
||||
this.breadCrumbs = observableOf(breadcrumbs);
|
||||
})
|
||||
}
|
||||
});
|
||||
this.publicMode = true;
|
||||
|
@ -284,6 +329,7 @@ export class DatasetWizardComponent extends BaseComponent implements OnInit, IBr
|
|||
// if (this.viewOnly) { this.formGroup.disable(); } // For future use, to make Dataset edit like DMP.
|
||||
this.loadDatasetProfiles();
|
||||
});
|
||||
|
||||
} else {
|
||||
this.datasetWizardModel = new DatasetWizardEditorModel();
|
||||
this.formGroup = this.datasetWizardModel.buildForm();
|
||||
|
@ -368,7 +414,21 @@ export class DatasetWizardComponent extends BaseComponent implements OnInit, IBr
|
|||
}
|
||||
|
||||
public cancel(): void {
|
||||
this.router.navigate(['/datasets']);
|
||||
if (!isNullOrUndefined(this.lock)) {
|
||||
this.lockService.unlockTarget(this.datasetWizardModel.id).pipe(takeUntil(this._destroyed)).subscribe(
|
||||
complete => {
|
||||
this.router.navigate(['/datasets']);
|
||||
},
|
||||
error => {
|
||||
this.formGroup.get('status').setValue(DmpStatus.Draft);
|
||||
this.onCallbackError(error);
|
||||
}
|
||||
|
||||
)
|
||||
} else {
|
||||
this.router.navigate(['/datasets']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
getDatasetDisplay(item: any): string {
|
||||
|
@ -679,7 +739,7 @@ export class DatasetWizardComponent extends BaseComponent implements OnInit, IBr
|
|||
return false;
|
||||
}
|
||||
else {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -706,4 +766,15 @@ export class DatasetWizardComponent extends BaseComponent implements OnInit, IBr
|
|||
onStepFound(linkToScroll: LinkToScroll) {
|
||||
this.linkToScroll = linkToScroll;
|
||||
}
|
||||
|
||||
private pumpLock() {
|
||||
this.lock.touchedAt = new Date();
|
||||
this.lockService.createOrUpdate(this.lock).pipe(takeUntil(this._destroyed)).subscribe( async result => {
|
||||
if (!isNullOrUndefined(result)) {
|
||||
this.lock.id = Guid.parse(result);
|
||||
} else {
|
||||
this.location.back();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<h4 class="card-title">{{ 'DMP-EDITOR.TITLE.NEW' | translate }}</h4>
|
||||
</div>
|
||||
<div class="d-flex ml-auto p-2" *ngIf="!isNew">
|
||||
<button *ngIf="!isFinalized" mat-icon-button [matMenuTriggerFor]="actionsMenu" class="ml-auto more-icon" (click)="$event.stopImmediatePropagation();">
|
||||
<button *ngIf="!isFinalized && !lockStatus" mat-icon-button [matMenuTriggerFor]="actionsMenu" class="ml-auto more-icon" (click)="$event.stopImmediatePropagation();">
|
||||
<mat-icon class="more-horiz">more_horiz</mat-icon>
|
||||
</button>
|
||||
<mat-menu #actionsMenu="matMenu">
|
||||
|
@ -65,21 +65,21 @@
|
|||
<mat-icon class="mr-2">work_outline</mat-icon>
|
||||
{{ 'DMP-LISTING.COLUMNS.GRANT' | translate }}
|
||||
</ng-template>
|
||||
<app-grant-tab [grantformGroup]="formGroup.get('grant')" [projectFormGroup]="formGroup.get('project')" [funderFormGroup]="formGroup.get('funder')" [isFinalized]="isFinalized" [isNew]="isNew" [isUserOwner]="isUserOwner"></app-grant-tab>
|
||||
<app-grant-tab [grantformGroup]="formGroup.get('grant')" [projectFormGroup]="formGroup.get('project')" [funderFormGroup]="formGroup.get('funder')" [isFinalized]="isFinalized || lockStatus" [isNew]="isNew" [isUserOwner]="isUserOwner"></app-grant-tab>
|
||||
</mat-tab>
|
||||
<mat-tab *ngIf="!isNew">
|
||||
<ng-template mat-tab-label>
|
||||
<mat-icon class="mr-2">library_books</mat-icon>
|
||||
{{ 'DMP-LISTING.COLUMNS.DATASETS' | translate }}
|
||||
</ng-template>
|
||||
<app-datasets-tab [dmp]="dmp" [isPublic]="isPublic" [isFinalized]="isFinalized"></app-datasets-tab>
|
||||
<app-datasets-tab [dmp]="dmp" [isPublic]="isPublic" [isFinalized]="isFinalized || lockStatus"></app-datasets-tab>
|
||||
</mat-tab>
|
||||
<mat-tab *ngIf="!isNew">
|
||||
<ng-template mat-tab-label>
|
||||
<mat-icon class="mr-2">person</mat-icon>
|
||||
{{ 'DMP-LISTING.COLUMNS.PEOPLE' | translate }}
|
||||
</ng-template>
|
||||
<app-people-tab [formGroup]="formGroup" [dmp]="dmp" [isPublic]="isPublic" [isFinalized]="isFinalized"></app-people-tab>
|
||||
<app-people-tab [formGroup]="formGroup" [dmp]="dmp" [isPublic]="isPublic" [isFinalized]="isFinalized || lockStatus"></app-people-tab>
|
||||
</mat-tab>
|
||||
<mat-tab *ngIf="isNew" disabled>
|
||||
<ng-template mat-tab-label></ng-template>
|
||||
|
@ -101,12 +101,12 @@
|
|||
{{'DMP-EDITOR.ACTIONS.CANCEL' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="isNew">
|
||||
<div *ngIf="isNew && !lockStatus">
|
||||
<button mat-raised-button color="primary" (click)="cancel()" type="button" class="text-uppercase mr-2">
|
||||
{{'DMP-EDITOR.ACTIONS.CANCEL' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="formGroup.enabled">
|
||||
<div *ngIf="formGroup.enabled && !lockStatus">
|
||||
<button *ngIf="!isNew" mat-raised-button type="submit" class="text-uppercase dark-theme mr-2" color="primary">
|
||||
{{'DMP-EDITOR.ACTIONS.SAVE-CHANGES' | translate}}
|
||||
</button>
|
||||
|
@ -114,7 +114,7 @@
|
|||
{{'DMP-EDITOR.ACTIONS.SAVE' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="formGroup.enabled && !isNew">
|
||||
<div *ngIf="formGroup.enabled && !isNew && !lockStatus">
|
||||
<button type="button" mat-raised-button color="primary" class="text-uppercase mr-2" (click)="saveAndFinalize()">{{'DMP-EDITOR.ACTIONS.FINALISE' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -32,10 +32,16 @@ import { FormValidationErrorsDialogComponent } from '@common/forms/form-validati
|
|||
import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import * as FileSaver from 'file-saver';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { Observable, of as observableOf, interval } from 'rxjs';
|
||||
import { map, takeUntil } from 'rxjs/operators';
|
||||
import { Principal } from "@app/core/model/auth/Principal";
|
||||
import { Role } from "@app/core/common/enum/role";
|
||||
import { LockService } from '@app/core/services/lock/lock.service';
|
||||
import { Location } from '@angular/common';
|
||||
import { LockModel } from '@app/core/model/lock/lock.model';
|
||||
import { Guid } from '@common/types/guid';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
import { environment } from 'environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dmp-editor-component',
|
||||
|
@ -62,6 +68,8 @@ export class DmpEditorComponent extends BaseComponent implements OnInit, IBreadC
|
|||
selectedDmpProfileDefinition: DmpProfileDefinition;
|
||||
DynamicDmpFieldResolverComponent: any;
|
||||
isUserOwner: boolean = true;
|
||||
lock: LockModel;
|
||||
lockStatus: Boolean;
|
||||
|
||||
constructor(
|
||||
private dmpProfileService: DmpProfileService,
|
||||
|
@ -73,7 +81,8 @@ export class DmpEditorComponent extends BaseComponent implements OnInit, IBreadC
|
|||
private uiNotificationService: UiNotificationService,
|
||||
private authentication: AuthService,
|
||||
private authService: AuthService,
|
||||
private formService: FormService
|
||||
private formService: FormService,
|
||||
private lockService: LockService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
@ -104,6 +113,8 @@ export class DmpEditorComponent extends BaseComponent implements OnInit, IBreadC
|
|||
this.dmpService.getSingle(itemId).pipe(map(data => data as DmpModel))
|
||||
.pipe(takeUntil(this._destroyed))
|
||||
.subscribe(async data => {
|
||||
this.lockService.checkLockStatus(data.id).pipe(takeUntil(this._destroyed)).subscribe(lockStatus => {
|
||||
this.lockStatus = lockStatus;
|
||||
this.dmp = new DmpEditorModel();
|
||||
this.dmp.grant = new GrantTabModel();
|
||||
this.dmp.project = new ProjectFormModel();
|
||||
|
@ -115,12 +126,22 @@ export class DmpEditorComponent extends BaseComponent implements OnInit, IBreadC
|
|||
this.isFinalized = true;
|
||||
this.formGroup.disable();
|
||||
}
|
||||
|
||||
//this.registerFormEventsForDmpProfile(this.dmp.definition);
|
||||
if (!this.editMode || this.dmp.status === DmpStatus.Finalized) {
|
||||
if (!this.editMode || this.dmp.status === DmpStatus.Finalized || lockStatus) {
|
||||
this.isFinalized = true;
|
||||
this.formGroup.disable();
|
||||
}
|
||||
|
||||
if (this.isAuthenticated) {
|
||||
if (!lockStatus) {
|
||||
this.lock = new LockModel(data.id, this.getUserFromDMP());
|
||||
|
||||
this.lockService.createOrUpdate(this.lock).pipe(takeUntil(this._destroyed)).subscribe(async result => {
|
||||
this.lock.id = Guid.parse(result);
|
||||
interval(environment.lockInterval).pipe(takeUntil(this._destroyed)).subscribe(() => this.pumpLock());
|
||||
});
|
||||
}
|
||||
// if (!this.isAuthenticated) {
|
||||
const breadCrumbs = [];
|
||||
breadCrumbs.push({
|
||||
|
@ -139,6 +160,7 @@ export class DmpEditorComponent extends BaseComponent implements OnInit, IBreadC
|
|||
}
|
||||
this.associatedUsers = data.associatedUsers;
|
||||
this.people = data.users;
|
||||
})
|
||||
});
|
||||
} else if (publicId != null) {
|
||||
this.isNew = false;
|
||||
|
@ -146,6 +168,8 @@ export class DmpEditorComponent extends BaseComponent implements OnInit, IBreadC
|
|||
this.dmpService.getSinglePublic(publicId).pipe(map(data => data as DmpModel))
|
||||
.pipe(takeUntil(this._destroyed))
|
||||
.subscribe(async data => {
|
||||
this.lockService.checkLockStatus(data.id).pipe(takeUntil(this._destroyed)).subscribe(lockStatus => {
|
||||
this.lockStatus = lockStatus;
|
||||
this.dmp = new DmpEditorModel();
|
||||
this.dmp.grant = new GrantTabModel();
|
||||
this.dmp.project = new ProjectFormModel();
|
||||
|
@ -153,7 +177,7 @@ export class DmpEditorComponent extends BaseComponent implements OnInit, IBreadC
|
|||
this.dmp.fromModel(data);
|
||||
this.formGroup = this.dmp.buildForm();
|
||||
//this.registerFormEventsForDmpProfile(this.dmp.definition);
|
||||
if (!this.editMode || this.dmp.status === DmpStatus.Finalized) { this.formGroup.disable(); }
|
||||
if (!this.editMode || this.dmp.status === DmpStatus.Finalized || lockStatus) { this.formGroup.disable(); }
|
||||
// if (!this.isAuthenticated) {
|
||||
const breadcrumbs = [];
|
||||
breadcrumbs.push({ parentComponentName: null, label: this.language.instant('NAV-BAR.PUBLIC-DMPS').toUpperCase(), url: '/plans' });
|
||||
|
@ -169,6 +193,15 @@ export class DmpEditorComponent extends BaseComponent implements OnInit, IBreadC
|
|||
// );
|
||||
this.associatedUsers = data.associatedUsers;
|
||||
// }
|
||||
if (!lockStatus) {
|
||||
this.lock = new LockModel(data.id, this.getUserFromDMP());
|
||||
|
||||
this.lockService.createOrUpdate(this.lock).pipe(takeUntil(this._destroyed)).subscribe(async result => {
|
||||
this.lock.id = Guid.parse(result);
|
||||
interval(environment.lockInterval).pipe(takeUntil(this._destroyed)).subscribe(() => this.pumpLock());
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
} else {
|
||||
this.dmp = new DmpEditorModel();
|
||||
|
@ -301,7 +334,17 @@ export class DmpEditorComponent extends BaseComponent implements OnInit, IBreadC
|
|||
|
||||
public cancel(id: String): void {
|
||||
if (id != null) {
|
||||
this.router.navigate(['/plans/overview/' + id]);
|
||||
this.lockService.unlockTarget(this.dmp.id).pipe(takeUntil(this._destroyed)).subscribe(
|
||||
complete => {
|
||||
this.router.navigate(['/plans/overview/' + id]);
|
||||
},
|
||||
error => {
|
||||
this.formGroup.get('status').setValue(DmpStatus.Draft);
|
||||
this.onCallbackError(error);
|
||||
}
|
||||
|
||||
)
|
||||
|
||||
} else {
|
||||
this.router.navigate(['/plans']);
|
||||
}
|
||||
|
@ -498,6 +541,17 @@ export class DmpEditorComponent extends BaseComponent implements OnInit, IBreadC
|
|||
});
|
||||
}
|
||||
|
||||
private pumpLock() {
|
||||
this.lock.touchedAt = new Date();
|
||||
this.lockService.createOrUpdate(this.lock).pipe(takeUntil(this._destroyed)).subscribe( async result => {
|
||||
if (!isNullOrUndefined(result)) {
|
||||
this.lock.id = Guid.parse(result);
|
||||
} else {
|
||||
this.location.back();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// advancedClicked() {
|
||||
// const dialogRef = this.dialog.open(ExportMethodDialogComponent, {
|
||||
// maxWidth: '500px',
|
||||
|
|
|
@ -477,6 +477,7 @@
|
|||
"FINALIZE": "Finalize",
|
||||
"REVERSE": "Undo Finalization",
|
||||
"INFO": "Datasets of finalized DMPs can't revert to unfinalized",
|
||||
"LOCK": "Dataset is Locked by another user",
|
||||
"DOWNLOAD-PDF": "Download PDF",
|
||||
"DOWNLOAD-XML": "Download XML",
|
||||
"DOWNLOAD-DOCX": "Download DOCX",
|
||||
|
|
|
@ -477,6 +477,7 @@
|
|||
"FINALIZE": "Finalize",
|
||||
"REVERSE": "Undo Finalization",
|
||||
"INFO": "Datasets of finalized DMPs can't revert to unfinalized",
|
||||
"LOCK": "Dataset is Locked by another user",
|
||||
"DOWNLOAD-PDF": "Download PDF",
|
||||
"DOWNLOAD-XML": "Download XML",
|
||||
"DOWNLOAD-DOCX": "Download DOCX",
|
||||
|
|
|
@ -43,4 +43,5 @@ export const environment = {
|
|||
enabled: true,
|
||||
logLevels: ["debug", "info", "warning", "error"]
|
||||
},
|
||||
lockInterval: 60000,
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue