Commit 066d051a authored by Maxym Borodenko's avatar Maxym Borodenko Committed by Viacheslav Pavlov

Versioned Subsets

Added currentVersion link to subset
Added currentVersion checks to SubsetRestControllerTest

Now after creating of new subset version accessionRefs and creators are copied too
parent 9a358144
......@@ -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.
......@@ -20,11 +20,14 @@ import static org.genesys2.server.model.impl.QSubsetCreator.subsetCreator;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
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;
......@@ -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;
......@@ -105,6 +110,10 @@ public class SubsetServiceImpl implements SubsetService {
@Autowired
private SubsetRepository subsetRepository;
/** The subset versions repository. */
@Autowired
private SubsetVersionsRepository subsetVersionsRepository;
@Autowired
private AccessionRepository accessionRepository;
......@@ -140,7 +149,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 +192,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 +255,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 +290,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 +458,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 +501,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 +803,29 @@ 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.setCreators(new ArrayList<>(source.getCreators()));
Subset saved = lazyLoad(subsetRepository.save(subset));
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