Commit 3b833acf authored by Matija Obreza's avatar Matija Obreza

Merge branch '421-versioned-subsets' into 'master'

Resolve "Versioned Subsets"

Closes #421

See merge request genesys-pgr/genesys-server!364
parents 9a358144 141a2bd6
......@@ -300,4 +300,16 @@ public class SubsetController extends ApiBaseController {
}
}
/**
* Create a new version of Subset based on an existing published Subset.
*
* @param uuid Subset UUID
* @return the new version of Subset
*/
@PostMapping(value = "/create-new-version")
public Subset createNewVersion(@RequestParam(value = "uuid", required = true) final UUID uuid) {
final Subset subset = subsetService.loadSubset(uuid);
return subsetService.createNewVersion(subset);
}
}
......@@ -18,6 +18,7 @@ package org.genesys2.server.model.impl;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
......@@ -32,12 +33,16 @@ import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.PostLoad;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonGetter;
import org.genesys.blocks.auditlog.annotations.Audited;
import org.genesys.blocks.model.JsonViews;
import org.genesys.blocks.model.SelfCleaning;
......@@ -57,13 +62,21 @@ import com.fasterxml.jackson.annotation.JsonView;
* The Class Subset.
*/
@Entity
@Table(name = "subset")
@Table(name = "subset", uniqueConstraints = @UniqueConstraint(name = "UQ_current_subs_version", columnNames={"versionsId", "current"}))
@Audited
public class Subset extends UuidModel implements AclAwareModel, SelfCleaning {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 7021405309572916429L;
/** The versions. */
@ManyToOne(cascade = { CascadeType.MERGE, CascadeType.REFRESH }, optional = false)
@JoinColumn(name = "versionsId", updatable = false)
@JsonIgnore
private SubsetVersions versions;
private Boolean current;
/** The title. */
@NotNull
@Size(max = 250)
......@@ -142,6 +155,17 @@ public class Subset extends UuidModel implements AclAwareModel, SelfCleaning {
@Column(length = 200)
private String source;
@Transient
private UUID currentVersion;
@PostLoad
protected void postLoad() {
if (this.versions != null && versions.getCurrentVersion() != null && !this.uuid.equals(versions.getCurrentVersion().getUuid())) {
this.currentVersion = versions.getCurrentVersion().getUuid();
}
}
/**
* Generate UUID if missing.
*/
......@@ -166,6 +190,42 @@ public class Subset extends UuidModel implements AclAwareModel, SelfCleaning {
return this.owner;
}
/**
* Gets the versions.
*
* @return the versions
*/
public SubsetVersions getVersions() {
return versions;
}
/**
* Sets the versions.
*
* @param versions the versions to set
*/
public void setVersions(final SubsetVersions versions) {
this.versions = versions;
}
/**
* Gets the current.
*
* @return the current
*/
public Boolean getCurrent() {
return current;
}
/**
* Sets current value
*
* @param current the new value of current
*/
public void setCurrent(final Boolean current) {
this.current = current;
}
/**
* Checks if published.
*
......@@ -418,4 +478,13 @@ public class Subset extends UuidModel implements AclAwareModel, SelfCleaning {
public void setOwner(Partner owner) {
this.owner = owner;
}
@JsonGetter
public UUID getCurrentVersion() {
return currentVersion;
}
public void setCurrentVersion(UUID currentVersion) {
this.currentVersion = currentVersion;
}
}
/*
* Copyright 2019 Global Crop Diversity Trust
*
* 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.genesys2.server.model.impl;
import org.genesys.blocks.model.UuidModel;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import java.util.List;
/**
* A {@link SubsetVersions} has at least one {@link Subset} record.
*
* @author Maxym Borodenko
*/
@Entity
@Table(name="subset_version")
public class SubsetVersions extends UuidModel {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 6558015151866843898L;
/** The current version. */
@OneToOne(cascade = {}, orphanRemoval = true)
private Subset currentVersion;
/** The all versions. */
@OneToMany(cascade = {}, fetch = FetchType.LAZY, mappedBy = "versions", orphanRemoval = true)
private List<Subset> allVersions;
/**
* Gets the current version.
*
* @return the currentVersion
*/
public Subset getCurrentVersion() {
return currentVersion;
}
/**
* Sets the current version.
*
* @param currentVersion the currentVersion to set
*/
public void setCurrentVersion(final Subset currentVersion) {
this.currentVersion = currentVersion;
}
/**
* Gets the all versions.
*
* @return the allVersions
*/
public List<Subset> getAllVersions() {
return allVersions;
}
/**
* Sets the all versions.
*
* @param allVersions the allVersions to set
*/
public void setAllVersions(final List<Subset> allVersions) {
this.allVersions = allVersions;
}
}
/*
* Copyright 2019 Global Crop Diversity Trust
*
* 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.genesys2.server.persistence;
import org.genesys2.server.model.impl.SubsetVersions;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.stereotype.Repository;
/**
* The Interface SubsetVersionsRepository.
*
* @author Maxym Borodenko
*/
@Repository
public interface SubsetVersionsRepository extends JpaRepository<SubsetVersions, Long>, QueryDslPredicateExecutor<SubsetVersions> {
}
/*
* Copyright 2018 Global Crop Diversity Trust
* Copyright 2019 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -301,4 +301,12 @@ public interface SubsetService {
* @param outputStream the stream
*/
void writeXlsxMCPD(Subset subset, OutputStream outputStream) throws IOException;
/**
* Method creating a new version of Subset based on an existing published Subset.
*
* @param source the source
* @return saved Subset in db.
*/
Subset createNewVersion(@Valid Subset source);
}
/*
* Copyright 2018 Global Crop Diversity Trust
* Copyright 2019 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -21,18 +21,21 @@ import static org.genesys2.server.model.impl.QSubsetCreator.subsetCreator;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.validation.Valid;
import com.querydsl.jpa.JPAExpressions;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.genesys.blocks.security.service.CustomAclService;
......@@ -51,11 +54,13 @@ import org.genesys2.server.model.impl.QSubsetAccessionRef;
import org.genesys2.server.model.impl.Subset;
import org.genesys2.server.model.impl.SubsetAccessionRef;
import org.genesys2.server.model.impl.SubsetCreator;
import org.genesys2.server.model.impl.SubsetVersions;
import org.genesys2.server.persistence.AccessionRepository;
import org.genesys2.server.persistence.FaoInstituteRepository;
import org.genesys2.server.persistence.SubsetAccessionRefRepository;
import org.genesys2.server.persistence.SubsetCreatorRepository;
import org.genesys2.server.persistence.SubsetRepository;
import org.genesys2.server.persistence.SubsetVersionsRepository;
import org.genesys2.server.service.DownloadService;
import org.genesys2.server.service.SubsetService;
import org.genesys2.server.service.filter.SubsetFilter;
......@@ -89,11 +94,14 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Predicate;
import com.querydsl.jpa.JPAExpressions;
/**
* The Class SubsetServiceImpl.
*
* @author Maxym Borodenko
* @author Matija Obreza
* @author Viacheslav Pavlov
*/
@Service
@Transactional(readOnly = true)
......@@ -102,9 +110,16 @@ public class SubsetServiceImpl implements SubsetService {
private static final Logger LOG = LoggerFactory.getLogger(SubsetServiceImpl.class);
@PersistenceContext
private EntityManager entityManager;
@Autowired
private SubsetRepository subsetRepository;
/** The subset versions repository. */
@Autowired
private SubsetVersionsRepository subsetVersionsRepository;
@Autowired
private AccessionRepository accessionRepository;
......@@ -140,7 +155,7 @@ public class SubsetServiceImpl implements SubsetService {
@Override
public Page<Subset> list(final SubsetFilter filter, final Pageable page) {
final BooleanBuilder published = new BooleanBuilder();
published.and(QSubset.subset.state.eq(PublishState.PUBLISHED));
published.and(QSubset.subset.state.eq(PublishState.PUBLISHED).and(QSubset.subset.current.isTrue()));
published.and(filter.buildPredicate());
return subsetRepository.findAll(published, page);
}
......@@ -183,9 +198,15 @@ public class SubsetServiceImpl implements SubsetService {
if (! StringUtils.equals(institute.getCode(), source.getWiewsCode())) {
throw new InvalidApiUsageException("Institute code of the subset does not match the code of Institute");
}
final SubsetVersions subsetVersions = new SubsetVersions();
subsetVersions.setCurrentVersion(null);
subsetVersionsRepository.save(subsetVersions);
final Subset subset = new Subset();
copyValues(subset, source);
subset.setState(PublishState.DRAFT);
subset.setVersions(subsetVersions);
subset.setCurrent(null);
Subset loaded = subsetRepository.save(subset);
......@@ -240,10 +261,28 @@ public class SubsetServiceImpl implements SubsetService {
subset.getCrops().size();
if (subset.getCreators() != null)
subset.getCreators().size();
if (subset.getAccessionRefs() != null) {
subset.getAccessionRefs().size();
lazyLoad(subset.getAccessionRefs());
}
if (subset.getVersions() != null && subset.getVersions().getAllVersions() != null) {
subset.getVersions().getAllVersions().size();
}
return subset;
}
private Iterable<SubsetAccessionRef> lazyLoad(final Iterable<SubsetAccessionRef> accessionRefs) {
if(accessionRefs != null) {
for (AccessionRef accessionRef: accessionRefs) {
if(accessionRef.getAccession() != null)
accessionRef.getAccession().getId();
}
}
return accessionRefs;
}
/**
* {@inheritDoc}
*/
......@@ -257,6 +296,7 @@ public class SubsetServiceImpl implements SubsetService {
* @see org.genesys2.server.service.SubsetService#loadSubset(java.util.UUID)
*/
@Override
@PostAuthorize("hasRole('ADMINISTRATOR') || returnObject==null || returnObject.isPublished() || hasPermission(returnObject, 'read')")
public Subset loadSubset(UUID uuid) {
return lazyLoad(getSubset(uuid));
}
......@@ -424,7 +464,17 @@ public class SubsetServiceImpl implements SubsetService {
// Make dataset publicly readable
aclService.makePubliclyReadable(loaded, true);
final SubsetVersions subsetVersions = loaded.getVersions();
final Subset oldCurrentSubset = subsetVersions.getAllVersions().stream().filter(s -> Objects.equals(s.getCurrent(), Boolean.TRUE)).findFirst().orElse(null);
if (oldCurrentSubset != null) {
oldCurrentSubset.setCurrent(null);
subsetRepository.save(oldCurrentSubset);
}
loaded.setCurrent(Boolean.TRUE);
subsetVersions.setCurrentVersion(loaded);
subsetVersionsRepository.save(subsetVersions);
return lazyLoad(subsetRepository.save(loaded));
}
......@@ -457,17 +507,41 @@ public class SubsetServiceImpl implements SubsetService {
@Transactional
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#subset, 'administration')")
public Subset rejectSubset(final Subset subset) {
final Subset loaded = subsetRepository.getByUuidAndVersion(subset.getUuid(), subset.getVersion());
Subset loaded = subsetRepository.getByUuidAndVersion(subset.getUuid(), subset.getVersion());
if (loaded == null) {
throw new NotFoundElement("No subset with specified uuid and version");
}
if (!securityUtils.hasRole(UserRole.ADMINISTRATOR) && loaded.getState().equals(PublishState.PUBLISHED)) {
if (loaded.isPublished() && !securityUtils.hasRole(UserRole.ADMINISTRATOR)) {
long oneDay = 24 * 60 * 60 * 1000;
if (loaded.getLastModifiedDate() != null && loaded.getLastModifiedDate().getTime() <= (System.currentTimeMillis() - oneDay)) {
throw new InvalidApiUsageException("Cannot be un-published. More than 24 hours have passed since the publication.");
}
}
if (loaded.isPublished() && Objects.equals(loaded.getCurrent(), true)) {
final SubsetVersions subsetVersions = loaded.getVersions();
if (subsetVersions.getAllVersions().size() > 1) {
UUID youngestSubsetUUID = subsetVersions.getAllVersions().stream()
.filter(s -> s.getCurrent() == null)
.max(Comparator.comparing(Subset::getCreatedDate)).get().getUuid();
loaded.setCurrent(null);
subsetRepository.save(loaded);
Subset youngestSubset = subsetRepository.getByUuid(youngestSubsetUUID);
youngestSubset.setCurrent(true);
subsetRepository.save(youngestSubset);
subsetVersions.setCurrentVersion(youngestSubset);
} else {
loaded.setCurrent(null);
subsetVersions.setCurrentVersion(null);
}
subsetVersionsRepository.save(subsetVersions);
} else if (loaded.isPublished() && Objects.isNull(loaded.getCurrent())) {
throw new InvalidApiUsageException("Cannot be un-published. The subset is not the latest version.");
}
loaded.setState(PublishState.DRAFT);
// Make Subset publicly not-readable
......@@ -735,4 +809,38 @@ public class SubsetServiceImpl implements SubsetService {
.select(QSubsetAccessionRef.subsetAccessionRef.accession.id).where(QSubsetAccessionRef.subsetAccessionRef.subset.eq(subset)));
downloadService.writeXlsxMCPD(predicate, outputStream);
}
/**
* {@inheritDoc}
*/
@Override
@Transactional
@PreAuthorize("(hasRole('ADMINISTRATOR') || hasPermission(#source, 'write')) && #source.published")
public Subset createNewVersion(@Valid final Subset source) {
final Subset subset = new Subset();
copyValues(subset, source);
subset.setState(PublishState.DRAFT);
subset.setCurrent(null);
subset.setUuid(null);
subset.setVersions(source.getVersions());
Subset saved = lazyLoad(subsetRepository.save(subset));
// Copy creators
source.getCreators().forEach(creator -> {
entityManager.detach(creator);
creator.setSubset(subset);
creator.setId(null);
creator.setVersion(null);
creator.setUuid(null);
subsetCreatorRepository.save(creator);
});
setAccessionRefs(saved, new HashSet<>(source.getAccessionRefs()));
saved.setCurrentVersion(source.getUuid());
// Make Subset publicly not-readable
aclService.makePubliclyReadable(saved, false);
return lazyLoad(saved);
}
}
......@@ -5627,3 +5627,145 @@ databaseChangeLog:
columnDataType: bigint(20)
columnName: owner_id
tableName: subset
- changeSet:
id: 1551087846304-1
author: mborodenko
comment: Create subset_version table
changes:
- createTable:
columns:
- column:
autoIncrement: true
constraints:
primaryKey: true
name: id
type: BIGINT
- column:
constraints:
nullable: false
name: active
type: BIT(1)
- column:
constraints:
nullable: false
name: version
type: INT
- column:
name: createdBy
type: BIGINT
- column:
name: createdDate
type: datetime(6)
- column:
name: lastModifiedBy
type: BIGINT
- column:
name: lastModifiedDate
type: datetime(6)
- column:
constraints:
nullable: false
name: uuid
type: BINARY(16)
- column:
name: currentVersion_id
type: BIGINT
tableName: subset_version
- changeSet:
id: 1551087846304-2
author: mborodenko
comment: Extend subset table
changes:
- addColumn:
tableName: subset
columns:
- column:
name: current
type: BIT(1)
defaultValue: null
- addColumn:
tableName: subset
columns:
- column:
constraints:
nullable: false
name: versionsId
type: BIGINT
- changeSet:
id: 1551087846304-3
author: mborodenko
changes:
- sql:
comment: Fill the subset_version table
sql: insert into subset_version
(active, version, createdBy, createdDate, lastModifiedBy, lastModifiedDate, uuid, currentVersion_id)
select
1, 1, null, now(), null, now(), unhex(replace(uuid(),'-','')), subset.id
from subset;
- sql:
comment: Insert data to subset#versionsId
sql: update subset set versionsId = (select id from subset_version where currentVersion_id = subset.id)
- sql:
comment: subset_version#currentVersion_id should be null if subset is not published
sql: update subset_version sb inner join subset s on sb.currentVersion_id = s.id
set sb.currentVersion_id = null where s.state != 1
- sql:
comment: subset#current should be true if subset is published
sql: update subset set current = true where state = 1
- changeSet:
id: 1551087846304-4
author: mborodenko
comment: Add indexes to subset and subset_version
changes:
- createIndex:
columns:
- column:
name: versionsId
indexName: FK_oqburc3cc36bkxc5xqf1f115n
tableName: subset
- createIndex:
columns:
- column:
name: currentVersion_id
indexName: FK_s7hy9ibde1l72318kgaqh21ae
tableName: subset_version
- changeSet:
id: 1551087846304-5
author: mborodenko
comment: Add unique constraint
changes:
- addUniqueConstraint:
columnNames: uuid
constraintName: UK_e5peorfw5vvs79mxpcxd8bi2r
tableName: subset_version
- changeSet:
id: 1551087846304-6
author: mborodenko
comment: Add foreign keys to subset and subset_version
changes:
- addForeignKeyConstraint:
baseColumnNames: currentVersion_id
baseTableName: subset_version
constraintName: FK_s7hy9ibde1l72318kgaqh21ae
deferrable: false
initiallyDeferred: false
onDelete: NO ACTION
onUpdate: NO ACTION
referencedColumnNames: id
referencedTableName: subset
- addForeignKeyConstraint:
baseColumnNames: versionsId
baseTableName: subset
constraintName: FK_oqburc3cc36bkxc5xqf1f115n2
deferrable: false
initiallyDeferred: false
onDelete: NO ACTION
onUpdate: NO ACTION
referencedColumnNames: id
referencedTableName: subset_version
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment