Skip to content

Commit

Permalink
Add support for full set of locking and versioning methods to rest api
Browse files Browse the repository at this point in the history
- still work in progress implementation
  • Loading branch information
Paul Warren committed Oct 16, 2018
1 parent 835e15b commit d943e10
Show file tree
Hide file tree
Showing 13 changed files with 172 additions and 59 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Integrates with Spring Data, Spring Data REST and Apache Solr</description>

<properties>
<ginkgo4j-version>1.0.9</ginkgo4j-version>
<springboot-version>2.0.4.RELEASE</springboot-version>
<springboot-version>2.0.5.RELEASE</springboot-version>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.io.Resource;
import org.springframework.core.io.WritableResource;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

Expand Down Expand Up @@ -125,8 +126,10 @@ public void setContent(S entity, InputStream content) {
os = ((WritableResource) resource).getOutputStream();
IOUtils.copy(content, os);
}
}
catch (IOException e) {
} catch (IOException e) {
logger.error(format("Unexpected io error setting content for entity %s", entity), e);
throw new StoreAccessException(format("Setting content for entity %s", entity), e);
} catch (Exception e) {
logger.error(format("Unexpected error setting content for entity %s", entity), e);
throw new StoreAccessException(format("Setting content for entity %s", entity), e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,11 @@ protected HandlerMethod lookupHandlerMethod(String lookupPath,
if (path.length < 3)
return null;

// HACKETY HACK HACK: let repository rest resource handle locks
if (path[path.length - 1].equals("version") ||
// HACKETY HACK HACK: let repository rest resource handle locking and versioning
// making those handler's consume and produce json will probably fix this
if (path[path.length - 1].equals("lock") ||
path[path.length - 1].equals("unlock") ||
path[path.length - 1].equals("version") ||
path[path.length - 1].equals("findAllLatestVersion") ||
path[path.length - 1].equals("findAllVersions")) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
public class RepositoryEntityLockController implements ApplicationEventPublisherAware {

private static final String ENTITY_LOCK_MAPPING = "/{repository}/{id}/lock";
private static final String ENTITY_VERSION_MAPPING = "/{repository}/{id}/version";
private static final String ENTITY_VERSION_MAPPING = "/{repository}/{id}/versionWithEntity";

private static Method LOCK_METHOD = null;

Expand Down Expand Up @@ -88,21 +88,4 @@ public ResponseEntity<Resource<?>> getLock(@PathVariable String id,
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}

@ResponseBody
@RequestMapping(value = ENTITY_LOCK_MAPPING, method = RequestMethod.PUT)
public ResponseEntity<Resource<?>> setLock(@PathVariable String id,
Principal principal)
throws ResourceNotFoundException, HttpRequestMethodNotSupportedException {

if (locker == null) {
throw new IllegalStateException("locking and versioning not enabled");
}

if (locker.lock(id, principal)) {
return new ResponseEntity<>(new LockResource(new Lock(id, principal.getName())), HttpStatus.OK);
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,26 @@
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.repository.support.RepositoryInvoker;
import org.springframework.data.rest.webmvc.support.DefaultedPageable;
import org.springframework.data.rest.extensions.versioning.LockResource;
import org.springframework.data.rest.webmvc.support.DefaultedPageable;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.core.EmbeddedWrappers;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ReflectionUtils;
import org.springframework.versions.Lock;
import org.springframework.versions.LockingAndVersioningRepository;
import org.springframework.versions.VersionInfo;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
Expand Down Expand Up @@ -127,8 +134,8 @@ public boolean matches(ContentStoreInfo info) {
throw new BadRequestException();
}

List contentIds = (List) reflectionService.invokeMethod(method, store,
keywords.get(0));
List contentIds = (List) reflectionService.invokeMethod(method, store,
keywords.get(0));

if (contentIds != null && contentIds.size() > 0) {
Class<?> entityType = repoInfo.getDomainType();
Expand Down Expand Up @@ -191,10 +198,55 @@ else if (source instanceof Iterable) {
}
}

private static final String ENTITY_LOCK_MAPPING = "/{repository}/{id}/lock";
private static Method LOCK_METHOD = null;
private static Method UNLOCK_METHOD = null;

static {
LOCK_METHOD = ReflectionUtils.findMethod(LockingAndVersioningRepository.class, "lock", Object.class);
UNLOCK_METHOD = ReflectionUtils.findMethod(LockingAndVersioningRepository.class, "unlock", Object.class);
}

@ResponseBody
@RequestMapping(value = ENTITY_LOCK_MAPPING, method = RequestMethod.PUT)
public ResponseEntity<org.springframework.hateoas.Resource<?>> lock(RootResourceInformation repoInfo,
@PathVariable String repository,
@PathVariable String id, Principal principal)
throws org.springframework.data.rest.webmvc.ResourceNotFoundException, HttpRequestMethodNotSupportedException {

Object domainObj = repoInfo.getInvoker().invokeFindById(id).get();

domainObj = ReflectionUtils.invokeMethod(LOCK_METHOD, repositories.getRepositoryFor(domainObj.getClass()).get(), domainObj);

if (domainObj != null) {
return new ResponseEntity<>(new LockResource(new Lock(id, principal.getName())), HttpStatus.OK);
} else {
return ResponseEntity.status(HttpStatus.CONFLICT).build();
}
}

@ResponseBody
@RequestMapping(value = ENTITY_LOCK_MAPPING, method = RequestMethod.DELETE)
public ResponseEntity<org.springframework.hateoas.Resource<?>> unlock(RootResourceInformation repoInfo,
@PathVariable String repository,
@PathVariable String id, Principal principal)
throws org.springframework.data.rest.webmvc.ResourceNotFoundException, HttpRequestMethodNotSupportedException {

Object domainObj = repoInfo.getInvoker().invokeFindById(id).get();

domainObj = ReflectionUtils.invokeMethod(UNLOCK_METHOD, repositories.getRepositoryFor(domainObj.getClass()).get(), domainObj);

if (domainObj != null) {
return ResponseEntity.ok().build();
} else {
return ResponseEntity.status(HttpStatus.CONFLICT).build();
}
}

private static Method VERSION_METHOD = null;

static {
VERSION_METHOD = ReflectionUtils.findMethod(LockingAndVersioningRepository.class, "version", Object.class);
VERSION_METHOD = ReflectionUtils.findMethod(LockingAndVersioningRepository.class, "version", Object.class, VersionInfo.class);
}

private static final String ENTITY_VERSION_MAPPING = "/{repository}/{id}/version";
Expand All @@ -203,18 +255,21 @@ else if (source instanceof Iterable) {
@RequestMapping(value = ENTITY_VERSION_MAPPING, method = RequestMethod.PUT)
public ResponseEntity<org.springframework.hateoas.Resource<?>> version(RootResourceInformation repoInfo,
@PathVariable String repository,
@PathVariable String id, Principal principal)
@PathVariable String id,
@RequestBody VersionInfo info,
Principal principal,
PersistentEntityResourceAssembler assembler)
throws org.springframework.data.rest.webmvc.ResourceNotFoundException, HttpRequestMethodNotSupportedException {

Object domainObj = repoInfo.getInvoker().invokeFindById(id).get();

System.out.println(VERSION_METHOD);
System.out.println(repositories.getRepositoryFor(domainObj.getClass()).get());
System.out.println(domainObj);

domainObj = ReflectionUtils.invokeMethod(VERSION_METHOD, repositories.getRepositoryFor(domainObj.getClass()).get(), domainObj);
domainObj = ReflectionUtils.invokeMethod(VERSION_METHOD, repositories.getRepositoryFor(domainObj.getClass()).get(), domainObj, info);

return ResponseEntity.ok().build();
if (domainObj != null) {
return new ResponseEntity(assembler.toResource(domainObj), HttpStatus.OK);
} else {
return ResponseEntity.status(HttpStatus.CONFLICT).build();
}
}

private static Method FINDALLLATESTVERSION_METHOD = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class TestEntity {
public @OriginalFileName String originalFileName;
public @ContentLength Long len;
public @MimeType String mimeType;
// public @Version Integer version;
// public @Version Integer versionWithEntity;

public Long getId() {
return id;
Expand Down Expand Up @@ -68,7 +68,7 @@ public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}

// public Integer getVersion() { return version; }
// public Integer getNumber() { return versionWithEntity; }
//
// public void setVersion(Integer version) { this.version = version; }
// public void setNumber(Integer versionWithEntity) { this.versionWithEntity = versionWithEntity; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.springframework.versions;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;

@Retention(RetentionPolicy.RUNTIME)
@Target(value = { FIELD, METHOD })
public @interface ContentVersion {
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,22 @@ public interface LockingAndVersioningRepository<T, ID extends Serializable> {
<S extends T> S save(S entity);

/**
* Creates a new version of the entity. This new version becomes the latest version. The given entity becomes the
* previous version and its lock is removed.
* Creates a new versionWithEntity of the entity. This new versionWithEntity becomes the latest versionWithEntity. The given entity becomes the
* previous versionWithEntity and its lock is removed.
*
* @param entity the entity to base the new version on
* @return the new version
* After updating the existing entity's versioning attributes, persists and flushes those changes this method then
* detaches the entity from the persistence context to effect a 'clone' that becomes the new versionWithEntity.
*
* @param entity the entity to base the new versionWithEntity on
* @return the new versionWithEntity
*/
<S extends T> S version(S entity);
<S extends T> S version(S entity, VersionInfo info);

/**
* Returns the latest version of all entities. When using LockingAndVersioningRepository this method would
* Returns the latest versionWithEntity of all entities. When using LockingAndVersioningRepository this method would
* usually be preferred over CrudRepository's findAll that would find all versions of all entities.
*
* @return list of latest version entities
* @return list of latest versionWithEntity entities
*/
@Query("select t from #{#entityName} t where t.latest = true")
<S extends T> List<S> findAllLatestVersion();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.springframework.versions;

public class VersionInfo {

private String number;
private String label;

public VersionInfo() {}

public VersionInfo(String number, String label) {
this.number = number;
this.label = label;
}

public String getNumber() {
return number;
}

public void setNumber(String number) {
this.number = number;
}

public String getLabel() {
return label;
}

public void setLabel(String label) {
this.label = label;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.springframework.versions;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;

@Retention(RetentionPolicy.RUNTIME)
@Target(value = { FIELD, METHOD })
public @interface VersionLabel {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.springframework.versions;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;

@Retention(RetentionPolicy.RUNTIME)
@Target(value = { FIELD, METHOD })
public @interface VersionNumber {
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.security.core.Authentication;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.persistence.EntityManager;
import javax.persistence.Id;
Expand Down Expand Up @@ -91,6 +90,8 @@ public <S extends T> S save(S entity) {
}

if (entityInformation.isNew(entity)) {
BeanUtils.setFieldWithAnnotation(entity, VersionNumber.class, "1.0");

em.persist(entity);
return entity;
}
Expand All @@ -108,14 +109,14 @@ public <S extends T> S save(S entity) {
}

@Transactional
public <S extends T> S version(S entity) {
public <S extends T> S version(S entity, VersionInfo info) {
Authentication authentication = auth.getAuthentication();
Object id = getId(entity);
if (id == null) return null;

Principal lockOwner;
if ((lockOwner = lockingService.lockOwner(id)) != null && authentication.getName().equals(lockOwner.getName()) == false) {
throw new LockOwnerException(format("entity not locked by you"));
throw new LockOwnerException(format("not lock owner"));
}

S old = em.find((Class<S>) entity.getClass(), id);
Expand All @@ -133,6 +134,8 @@ public <S extends T> S version(S entity) {
entity = clone(entity);
BeanUtils.setFieldWithAnnotation(entity, Id.class, null);
BeanUtils.setFieldWithAnnotation(entity, Version.class, 0);
BeanUtils.setFieldWithAnnotation(entity, VersionNumber.class, info.getNumber());
BeanUtils.setFieldWithAnnotation(entity, VersionLabel.class, info.getLabel());
entity = updateVersionAttributes(entity, id, getAncestralRootId(old), true);
em.persist(entity);
entity = this.lock(entity);
Expand Down
Loading

0 comments on commit d943e10

Please sign in to comment.