diff --git a/.gitignore b/.gitignore index e93ce03c..93ecf3be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ logs target +/.bloop /.idea /.idea_modules /.classpath diff --git a/app/Module.java b/app/Module.java index a4e04ee7..9f35de31 100644 --- a/app/Module.java +++ b/app/Module.java @@ -1,9 +1,11 @@ import com.google.inject.AbstractModule; import config.MetamodelProvider; import config.impl.JPAMetamodelProvider; +import dao.CommitDao; import dao.ElementDao; import dao.ProjectDao; import dao.RelationshipDao; +import dao.impl.jpa.JpaCommitDao; import dao.impl.jpa.JpaElementDao; import dao.impl.jpa.JpaProjectDao; import dao.impl.jpa.JpaRelationshipDao; @@ -22,5 +24,6 @@ protected void configure() { bind(ElementDao.class).to(JpaElementDao.class); bind(ProjectDao.class).to(JpaProjectDao.class); bind(RelationshipDao.class).to(JpaRelationshipDao.class); + bind(CommitDao.class).to(JpaCommitDao.class); } } \ No newline at end of file diff --git a/app/config/impl/JPAMetamodelProvider.java b/app/config/impl/JPAMetamodelProvider.java index acf03c50..4511192b 100644 --- a/app/config/impl/JPAMetamodelProvider.java +++ b/app/config/impl/JPAMetamodelProvider.java @@ -13,11 +13,11 @@ public class JPAMetamodelProvider implements MetamodelProvider { static { INTERFACES.add(MofObject.class); - for (String pakkage : new String[]{"org.omg.sysml.metamodel", "org.omg.sysml.extension"}) { + for (String pakkage : new String[]{"org.omg.sysml.metamodel", "org.omg.sysml.extension", "org.omg.sysml.versioning"}) { INTERFACES.addAll(new Reflections(pakkage).getSubTypesOf(MofObject.class)); } IMPLEMENTATION_CLASSES.add(MofObjectImpl.class); - for (String pakkage : new String[]{"org.omg.sysml.metamodel.impl", "org.omg.sysml.extension.impl"}) { + for (String pakkage : new String[]{"org.omg.sysml.metamodel.impl", "org.omg.sysml.extension.impl", "org.omg.sysml.versioning.impl"}) { IMPLEMENTATION_CLASSES.addAll(new Reflections(pakkage).getSubTypesOf(MofObjectImpl.class)); } } diff --git a/app/controllers/CommitController.java b/app/controllers/CommitController.java new file mode 100644 index 00000000..3b7fe9f6 --- /dev/null +++ b/app/controllers/CommitController.java @@ -0,0 +1,75 @@ +package controllers; + +import com.fasterxml.jackson.databind.JsonNode; +import config.MetamodelProvider; +import jackson.JacksonHelper; +import org.omg.sysml.lifecycle.Commit; +import org.omg.sysml.lifecycle.impl.CommitImpl; +import play.libs.Json; +import play.mvc.Controller; +import play.mvc.Http; +import play.mvc.Result; +import play.mvc.Results; +import services.CommitService; + +import javax.inject.Inject; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public class CommitController extends Controller { + @Inject + private MetamodelProvider metamodelProvider; + + @Inject + private CommitService commitService; + + public Result byId(String id) { + UUID uuid = UUID.fromString(id); + Optional commit = commitService.getById(uuid); + return commit.map(e -> ok(Json.toJson(e))).orElseGet(Results::notFound); + } + + public Result all() { + List commits = commitService.getAll(); + return ok(JacksonHelper.collectionValueToTree(List.class, metamodelProvider.getImplementationClass(Commit.class), commits)); + } + + public Result create(Http.Request request) { + JsonNode requestBodyJson = request.body().asJson(); + Commit requestedObject = Json.fromJson(requestBodyJson, metamodelProvider.getImplementationClass(Commit.class)); + if (requestedObject.getId() != null || requestedObject.getTimestamp() != null) { + return Results.badRequest(); + } + requestedObject.setTimestamp(ZonedDateTime.now()); + Optional responseCommit = commitService.create(requestedObject); + return responseCommit.map(e -> created(Json.toJson(e))).orElseGet(Results::internalServerError); + } + + public Result createWithProjectId(UUID projectId, Http.Request request) { + JsonNode requestBodyJson = request.body().asJson(); + Commit requestedObject = Json.fromJson(requestBodyJson, metamodelProvider.getImplementationClass(Commit.class)); + if (requestedObject.getId() != null || requestedObject.getTimestamp() != null) { + return Results.badRequest(); + } + requestedObject.setTimestamp(ZonedDateTime.now()); + Optional responseCommit = commitService.create(projectId, requestedObject); + return responseCommit.map(e -> created(Json.toJson(e))).orElseGet(Results::internalServerError); + } + + public Result byProject(UUID projectId) { + List commits = commitService.getByProjectId(projectId); + return ok(JacksonHelper.collectionValueToTree(List.class, metamodelProvider.getImplementationClass(Commit.class), commits, writer -> writer.withView(CommitImpl.Views.Compact.class))); + } + + public Result byProjectAndId(UUID projectId, UUID commitId) { + Optional commit = commitService.getByProjectIdAndId(projectId, commitId); + return commit.map(e -> ok(Json.toJson(e))).orElseGet(Results::notFound); + } + + public Result headByProject(UUID projectId) { + Optional commit = commitService.getHeadByProjectId(projectId); + return commit.map(e -> ok(Json.toJson(e))).orElseGet(Results::notFound); + } +} diff --git a/app/controllers/ElementController.java b/app/controllers/ElementController.java index db60fb1e..dd85920d 100644 --- a/app/controllers/ElementController.java +++ b/app/controllers/ElementController.java @@ -15,6 +15,7 @@ import javax.inject.Inject; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.UUID; /** @@ -47,19 +48,29 @@ public Result create(Http.Request request) { return Results.badRequest(); } Optional responseElement = elementService.create(((Element) requestedObject)); - return responseElement.map(e -> created(Json.toJson(e))).orElseGet(Results::badRequest); + return responseElement.map(e -> created(Json.toJson(e))).orElseGet(Results::internalServerError); } - public Result byProject(String projectId) { - UUID projectUuid = UUID.fromString(projectId); - List elements = elementService.getByProjectId(projectUuid); - return ok(JacksonHelper.collectionValueToTree(List.class, metamodelProvider.getImplementationClass(Element.class), elements)); + public Result byCommit(String commitId) { + UUID commitUuid = UUID.fromString(commitId); + Set elements = elementService.getByCommitId(commitUuid); + return ok(JacksonHelper.collectionValueToTree(Set.class, metamodelProvider.getImplementationClass(Element.class), elements)); } - public Result byProjectAndId(String elementId, String projectId) { + public Result byCommitAndId(String commitId, String elementId) { + UUID commitUuid = UUID.fromString(commitId); UUID elementUuid = UUID.fromString(elementId); - UUID projectUuid = UUID.fromString(projectId); - Optional element = elementService.getByProjectIdAndId(projectUuid, elementUuid); + Optional element = elementService.getByCommitIdAndId(commitUuid, elementUuid); + return element.map(e -> ok(Json.toJson(e))).orElseGet(Results::notFound); + } + + public Result getElementsByProjectIdCommitId(UUID projectId, UUID commitId) { + Set elements = elementService.getElementsByProjectIdCommitId(projectId, commitId); + return ok(JacksonHelper.collectionValueToTree(Set.class, metamodelProvider.getImplementationClass(Element.class), elements)); + } + + public Result getElementByProjectIdCommitIdElementId(UUID projectId, UUID commitId, UUID elementId) { + Optional element = elementService.getElementsByProjectIdCommitIdElementId(projectId, commitId, elementId); return element.map(e -> ok(Json.toJson(e))).orElseGet(Results::notFound); } } diff --git a/app/controllers/ProjectController.java b/app/controllers/ProjectController.java index 4339a92d..c65d210c 100644 --- a/app/controllers/ProjectController.java +++ b/app/controllers/ProjectController.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.databind.JsonNode; import config.MetamodelProvider; import jackson.JacksonHelper; -import org.omg.sysml.extension.Project; +import org.omg.sysml.lifecycle.Project; import play.libs.Json; import play.mvc.Controller; import play.mvc.Http; @@ -28,9 +28,8 @@ public class ProjectController extends Controller { @Inject private ProjectService projectService; - public Result byId(String id) { - UUID uuid = UUID.fromString(id); - Optional project = projectService.getById(uuid); + public Result byId(UUID id) { + Optional project = projectService.getById(id); return project.map(m -> ok(Json.toJson(m))).orElseGet(Results::notFound); } @@ -43,6 +42,6 @@ public Result create(Http.Request request) { JsonNode requestBodyJson = request.body().asJson(); Project requestProject = Json.fromJson(requestBodyJson, metamodelProvider.getImplementationClass(Project.class)); Optional responseProject = projectService.create(requestProject); - return responseProject.map(e -> created(Json.toJson(e))).orElseGet(Results::badRequest); + return responseProject.map(e -> created(Json.toJson(e))).orElseGet(Results::internalServerError); } } diff --git a/app/controllers/RelationshipController.java b/app/controllers/RelationshipController.java index c8f2fb33..be02df22 100644 --- a/app/controllers/RelationshipController.java +++ b/app/controllers/RelationshipController.java @@ -15,6 +15,7 @@ import javax.inject.Inject; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.UUID; /** @@ -47,30 +48,12 @@ public Result create(Http.Request request) { return Results.badRequest(); } Optional responseRelationship = relationshipService.create((Relationship) requestedObject); - return responseRelationship.map(e -> created(Json.toJson(e))).orElseGet(Results::badRequest); + return responseRelationship.map(e -> created(Json.toJson(e))).orElseGet(Results::internalServerError); } - public Result byRelatedElementId(String id) { - UUID elementUuid = UUID.fromString(id); - List relationships = relationshipService.getByRelatedElementId(elementUuid); - return ok(JacksonHelper.collectionValueToTree(List.class, metamodelProvider.getImplementationClass(Relationship.class), relationships)); - } - - public Result bySourceElementId(String id) { - UUID elementUuid = UUID.fromString(id); - List relationships = relationshipService.getBySourceElementId(elementUuid); - return ok(JacksonHelper.collectionValueToTree(List.class, metamodelProvider.getImplementationClass(Relationship.class), relationships)); - } - - public Result byTargetElementId(String id) { - UUID elementUuid = UUID.fromString(id); - List relationships = relationshipService.getByTargetElementId(elementUuid); - return ok(JacksonHelper.collectionValueToTree(List.class, metamodelProvider.getImplementationClass(Relationship.class), relationships)); - } - - public Result byProject(String projectId) { - UUID projectUuid = UUID.fromString(projectId); - List relationships = relationshipService.getByProjectId(projectUuid); - return ok(JacksonHelper.collectionValueToTree(List.class, metamodelProvider.getImplementationClass(Relationship.class), relationships)); + public Result getRelationshipsByProjectIdCommitIdRelatedElementId(UUID projectId, UUID commitId, UUID elementId) { + System.out.println(projectId + " : " + commitId + " : " + elementId); + Set relationships = relationshipService.getRelationshipsByProjectCommitRelatedElement(projectId, commitId, elementId); + return ok(JacksonHelper.collectionValueToTree(Set.class, metamodelProvider.getImplementationClass(Relationship.class), relationships)); } } diff --git a/app/dao/CommitDao.java b/app/dao/CommitDao.java new file mode 100644 index 00000000..edd9af0c --- /dev/null +++ b/app/dao/CommitDao.java @@ -0,0 +1,28 @@ +package dao; + +import org.omg.sysml.lifecycle.Project; +import org.omg.sysml.lifecycle.Commit; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface CommitDao extends Dao { + Optional persist(Commit commit); + + Optional update(Commit commit); + + Optional findById(UUID id); + + List findAll(); + + void delete(Commit commit); + + void deleteAll(); + + List findAllByProject(Project project); + + Optional findByProjectAndId(Project project, UUID id); + + Optional findHeadByProject(Project project); +} diff --git a/app/dao/ElementDao.java b/app/dao/ElementDao.java index 2a84c069..321a1617 100644 --- a/app/dao/ElementDao.java +++ b/app/dao/ElementDao.java @@ -1,26 +1,27 @@ package dao; -import org.omg.sysml.extension.Project; +import org.omg.sysml.lifecycle.Commit; import org.omg.sysml.metamodel.Element; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.UUID; public interface ElementDao extends Dao { - Optional persist(Element Element); + Optional persist(Element element); - Optional update(Element entity); + Optional update(Element element); Optional findById(UUID id); List findAll(); - void delete(Element Element); + void delete(Element element); void deleteAll(); - List findAllByProject(Project project); + Set findAllByCommit(Commit commit); - Optional findByProjectAndId(Project project, UUID id); + Optional findByCommitAndId(Commit commit, UUID id); } diff --git a/app/dao/ProjectDao.java b/app/dao/ProjectDao.java index cb0c92a3..5276570e 100644 --- a/app/dao/ProjectDao.java +++ b/app/dao/ProjectDao.java @@ -1,21 +1,21 @@ package dao; -import org.omg.sysml.extension.Project; +import org.omg.sysml.lifecycle.Project; import java.util.List; import java.util.Optional; import java.util.UUID; public interface ProjectDao extends Dao { - Optional persist(Project Project); + Optional persist(Project project); - Optional update(Project entity); + Optional update(Project project); Optional findById(UUID id); List findAll(); - void delete(Project Project); + void delete(Project project); void deleteAll(); } diff --git a/app/dao/RelationshipDao.java b/app/dao/RelationshipDao.java index 0bec8e2f..57c99a11 100644 --- a/app/dao/RelationshipDao.java +++ b/app/dao/RelationshipDao.java @@ -1,31 +1,27 @@ package dao; +import org.omg.sysml.lifecycle.Commit; +import org.omg.sysml.lifecycle.Project; import org.omg.sysml.metamodel.Element; -import org.omg.sysml.extension.Project; import org.omg.sysml.metamodel.Relationship; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.UUID; public interface RelationshipDao extends Dao { - Optional persist(Relationship Relationship); + Optional persist(Relationship relationship); - Optional update(Relationship entity); + Optional update(Relationship relationship); Optional findById(UUID id); List findAll(); - void delete(Relationship Relationship); + void delete(Relationship relationship); void deleteAll(); - List findAllByRelatedElement(Element element); - - List findAllBySourceElement(Element element); - - List findAllByTargetElement(Element element); - - List findAllByProject(Project project); + Set findAllByCommitRelatedElement(Commit commit, Element relatedElement); } diff --git a/app/dao/impl/jpa/JpaCommitDao.java b/app/dao/impl/jpa/JpaCommitDao.java new file mode 100644 index 00000000..c94e322f --- /dev/null +++ b/app/dao/impl/jpa/JpaCommitDao.java @@ -0,0 +1,269 @@ +package dao.impl.jpa; + +import dao.CommitDao; +import javabean.JavaBeanHelper; +import jpa.manager.JPAManager; +import org.omg.sysml.lifecycle.Commit; +import org.omg.sysml.lifecycle.ElementVersion; +import org.omg.sysml.lifecycle.Project; +import org.omg.sysml.lifecycle.impl.CommitImpl; +import org.omg.sysml.lifecycle.impl.CommitImpl_; +import org.omg.sysml.lifecycle.impl.ElementIdentityImpl; +import org.omg.sysml.lifecycle.impl.ElementVersionImpl; +import org.omg.sysml.metamodel.Element; +import org.omg.sysml.metamodel.MofObject; +import org.omg.sysml.metamodel.impl.MofObjectImpl; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.persistence.NoResultException; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaDelete; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; +import java.lang.reflect.Method; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Singleton +public class JpaCommitDao extends JpaDao implements CommitDao { + // TODO Explore alternative to serializing lazy entity attributes that doesn't involve resolving all proxies one level. + static Consumer PROXY_RESOLVER = commit -> commit.getChanges().stream().filter(Objects::nonNull).map(ElementVersion::getData).filter(mof -> mof instanceof Element).map(mof -> (Element) mof).forEach(JpaElementDao.PROXY_RESOLVER); + + @Inject + private JPAManager jpa; + + @Override + protected JPAManager getJpaManager() { + return jpa; + } + + private JpaElementDao elementDao = new JpaElementDao(); + + @Override + public Optional persist(Commit commit) { + MofObject tombstone = new MofObjectImpl() { + UUID id = UUID.randomUUID(); + + @Override + public UUID getId() { + return id; + } + }; + + Supplier> changeStream = () -> commit.getChanges().stream().filter(change -> change instanceof ElementVersionImpl).map(change -> (ElementVersionImpl) change); + + // Give all Commit#changes an identity, if they don't already have one, and all Commit#changes#identity an id, if they don't already have one. + changeStream.get().peek(change -> change.setIdentity(change.getIdentity() != null ? change.getIdentity() : new ElementIdentityImpl())).map(ElementVersion::getIdentity).filter(identity -> identity instanceof ElementIdentityImpl).map(identity -> (ElementIdentityImpl) identity).forEach(identity -> identity.setId(identity.getId() != null ? identity.getId() : UUID.randomUUID())); + + // Copy all Commit#changes#identity#id to Commit#changes#data#identifier and give all Commit#changes#data a random id. + Map identifierToMofMap = changeStream.get().peek(change -> Optional.ofNullable(change.getData()).filter(mof -> mof instanceof MofObjectImpl).map(mof -> (MofObjectImpl) mof).ifPresent(mof -> { + mof.setIdentifier(change.getIdentity().getId()); + mof.setId(UUID.randomUUID()); + })).collect(Collectors.toMap(change -> change.getIdentity().getId(), change -> Optional.ofNullable(change.getData()).orElse(tombstone))); + + // Attempt #1 using a javassist proxy. Failed because Hibernate/JPA can't handle subclasses of Entities. +/* changeStream.get().map(ElementVersion::getData).filter(Objects::nonNull).map(JpaCommitDao::getBeanPropertyValues).flatMap(map -> map.values().stream()).flatMap(o -> o instanceof Collection ? ((Collection) o).stream() : Stream.of(o)).filter(o -> o instanceof MofObject && o instanceof ProxyObject).map(o -> (MofObject & ProxyObject) o).forEach(mofProxy -> { + MofObject mof = identifierToMofMap.computeIfAbsent(mofProxy.getIdentifier(), identifier -> { + if (commit.getPreviousCommit() == null) { + return tombstone; + } + return elementDao.findByCommitAndId(commit.getPreviousCommit(), identifier).map(element -> (MofObject) element).orElse(tombstone); + }); + if (Objects.equals(mof, tombstone)) { + throw new IllegalArgumentException("Element with ID " + mofProxy.getIdentifier() + " not found."); + } + mofProxy.setHandler(new PassthroughMethodHandler(mof)); + System.out.println("REFERENCE: " + mofProxy.getIdentifier()); + });*/ + + Function reattachMofFunction = mof -> { + MofObject reattachedMof = identifierToMofMap.computeIfAbsent(mof.getIdentifier(), identifier -> { + if (commit.getPreviousCommit() == null) { + return tombstone; + } + return elementDao.findByCommitAndId(commit.getPreviousCommit(), identifier).map(element -> (MofObject) element).orElse(tombstone); + }); + if (Objects.equals(reattachedMof, tombstone)) { + throw new IllegalArgumentException("Element with ID " + mof.getIdentifier() + " not found."); + } + return reattachedMof; + }; + changeStream.get().map(ElementVersion::getData).filter(Objects::nonNull).forEach(mof -> JavaBeanHelper.getBeanProperties(mof).values().stream().filter(property -> property.getReadMethod() != null && property.getWriteMethod() != null).forEach(property -> { + Method getter = property.getReadMethod(); + Method setter = property.getWriteMethod(); + Class type = property.getPropertyType(); + + Object originalValue; + try { + originalValue = getter.invoke(mof); + final Object newValue; + if (MofObject.class.isAssignableFrom(type)) { + if (!(originalValue instanceof MofObject)) { + return; + } + newValue = reattachMofFunction.apply((MofObject) originalValue); + } else if (Collection.class.isAssignableFrom(type)) { + Collection originalValueCollection = (Collection) originalValue; + if (originalValueCollection.isEmpty() || originalValueCollection.stream().anyMatch((o -> !(o instanceof MofObject)))) { + return; + } + final Collection newValueCollection; + if (List.class.isAssignableFrom(type)) { + newValueCollection = new ArrayList<>(); + } else if (Set.class.isAssignableFrom(type)) { + newValueCollection = new HashSet<>(); + } else if (Collection.class.isAssignableFrom(type)) { + newValueCollection = new ArrayList<>(); + } else { + throw new IllegalStateException("Unknown collection type."); + } + ((Collection) originalValue).stream().map(o -> (MofObject) o).map(reattachMofFunction).forEachOrdered(newValueCollection::add); + newValue = newValueCollection; + } else { + return; + } + setter.invoke(mof, newValue); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + })); + + /* changeStream.get().map(ElementVersion::getData).filter(Objects::nonNull).filter(element -> element instanceof BlockImpl).map(element -> (BlockImpl) element).forEach(block -> { + if (block.getOwner() instanceof MofObjectImpl) { + MofObjectImpl owner = (MofObjectImpl) block.getOwner(); + if (owner.getIdentifier() != null) { + block.setOwner((Element) identifierToMofMap.get(owner.getIdentifier())); + } + } + });*/ + + // If previousCommit is not specified, default to head commit. + if (commit.getPreviousCommit() == null && commit.getContainingProject() != null) { + findHeadByProject(commit.getContainingProject()).ifPresent(commit::setPreviousCommit); + } + + return jpa.transact(em -> { + commit.getChanges().stream().map(ElementVersion::getData).filter(mof -> mof instanceof MofObjectImpl).map(mof -> (MofObjectImpl) mof).map(mof -> { + try { + MofObjectImpl firstPassMof = mof.getClass().getConstructor().newInstance(); + firstPassMof.setId(mof.getId()); + return firstPassMof; + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + }).forEach(em::merge); + commit.setChanges(commit.getChanges().stream().map(em::merge).collect(Collectors.toSet())); + return super.persist(commit, em); + }); + } + + @Override + public Optional findById(UUID id) { + return jpa.transact(em -> { + CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery(CommitImpl.class); + Root root = query.from(CommitImpl.class); + query.select(root) + .where(builder.equal(root.get(CommitImpl_.id), id)); + try { + return Optional.of(em.createQuery(query).getSingleResult()); + } catch (NoResultException e) { + return Optional.empty(); + } + }); + } + + @Override + public List findAll() { + return jpa.transact(em -> { + CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery(CommitImpl.class); + query.select(query.from(CommitImpl.class)); + return em.createQuery(query).getResultStream().collect(Collectors.toList()); + }); + } + + @Override + public void deleteAll() { + jpa.transact(em -> { + CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaDelete query = builder.createCriteriaDelete(CommitImpl.class); + query.from(CommitImpl.class); + return ((Stream) em.createQuery(query).getResultStream()).collect(Collectors.toList()); + }); + } + + @Override + public List findAllByProject(Project project) { + return jpa.transact(em -> { + CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery(CommitImpl.class); + Root root = query.from(CommitImpl.class); + query.select(root) + .where(builder.equal(root.get(CommitImpl_.containingProject), project)); + return em.createQuery(query).getResultStream().collect(Collectors.toList()); + }); + } + + @Override + public Optional findByProjectAndId(Project project, UUID id) { + return jpa.transact(em -> { + CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery(CommitImpl.class); + Root root = query.from(CommitImpl.class); + query.select(root) + .where(builder.and( + builder.equal(root.get(CommitImpl_.containingProject), project), + builder.equal(root.get(CommitImpl_.id), id) + )); + Optional commit; + try { + commit = Optional.of(em.createQuery(query).getSingleResult()); + } catch (NoResultException e) { + return Optional.empty(); + } + commit.ifPresent(PROXY_RESOLVER); + return commit; + }); + } + + @Override + public Optional findHeadByProject(Project project) { + return jpa.transact(em -> { + CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery(CommitImpl.class); + Root root = query.from(CommitImpl.class); + query.select(root) + .where(builder.equal(root.get(CommitImpl_.containingProject), project)) + .orderBy(builder.desc(root.get(CommitImpl_.timestamp))); + Optional commit; + try { + commit = Optional.of(em.createQuery(query).setMaxResults(1).getSingleResult()); + } catch (NoResultException e) { + return Optional.empty(); + } + commit.ifPresent(PROXY_RESOLVER); + return commit; + }); + } + +/* private static class PassthroughMethodHandler implements MethodHandler { + + private final Object target; + + protected PassthroughMethodHandler(Object target) { + this.target = target; + } + + @Override + public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable { + //return thisMethod.invoke(target, args); + return target.getClass().getMethod(thisMethod.getName(), thisMethod.getParameterTypes()).invoke(target, args); + } + }*/ +} diff --git a/app/dao/impl/jpa/JpaDao.java b/app/dao/impl/jpa/JpaDao.java index addb1fff..512f182c 100644 --- a/app/dao/impl/jpa/JpaDao.java +++ b/app/dao/impl/jpa/JpaDao.java @@ -3,6 +3,7 @@ import dao.Dao; import jpa.manager.JPAManager; +import javax.persistence.EntityManager; import javax.persistence.EntityTransaction; import java.util.Optional; @@ -23,13 +24,17 @@ public Optional update(E e) { @Override public Optional persist(E e) { - return Optional.ofNullable(getJpaManager().transact(em -> { - EntityTransaction transaction = em.getTransaction(); - transaction.begin(); - em.persist(e); - transaction.commit(); - return e; - })); + return getJpaManager().transact(em -> { + return persist(e, em); + }); + } + + protected Optional persist(E e, EntityManager em) { + EntityTransaction transaction = em.getTransaction(); + transaction.begin(); + em.persist(e); + transaction.commit(); + return Optional.ofNullable(e); } @Override diff --git a/app/dao/impl/jpa/JpaElementDao.java b/app/dao/impl/jpa/JpaElementDao.java index 36a11fb7..ad79b6c0 100644 --- a/app/dao/impl/jpa/JpaElementDao.java +++ b/app/dao/impl/jpa/JpaElementDao.java @@ -2,11 +2,9 @@ import config.MetamodelProvider; import dao.ElementDao; +import javabean.JavaBeanHelper; import jpa.manager.JPAManager; -import org.hibernate.Session; -import org.hibernate.SessionFactory; -import org.hibernate.query.Query; -import org.omg.sysml.extension.Project; +import org.omg.sysml.lifecycle.Commit; import org.omg.sysml.metamodel.Element; import org.omg.sysml.metamodel.impl.MofObjectImpl; import org.omg.sysml.metamodel.impl.MofObjectImpl_; @@ -15,14 +13,17 @@ import javax.inject.Singleton; import javax.persistence.NoResultException; import javax.persistence.criteria.*; -import java.util.List; -import java.util.Optional; -import java.util.UUID; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @Singleton public class JpaElementDao extends JpaDao implements ElementDao { + // TODO Explore alternative to serializing lazy entity attributes that doesn't involve resolving all proxies one level. + static Consumer PROXY_RESOLVER = element -> JavaBeanHelper.getBeanPropertyValues(element).values().stream().flatMap(o -> o instanceof Collection ? ((Collection) o).stream() : Stream.of(o)).forEach(o -> {}); + @Inject private MetamodelProvider metamodelProvider; @@ -41,7 +42,7 @@ public Optional findById(UUID id) { CriteriaQuery query = builder.createQuery(MofObjectImpl.class); Root root = query.from(MofObjectImpl.class); query.select(root).where(builder.and( - builder.equal(root.get(MofObjectImpl_.identifier), id), + builder.equal(root.get(MofObjectImpl_.id), id), getTypeExpression(builder, root) )); try { @@ -75,26 +76,49 @@ public void deleteAll() { } @Override - public List findAllByProject(Project project) { - try (Session session = jpa.getEntityManagerFactory().unwrap(SessionFactory.class).openSession()) { - Query query = session.createQuery("FROM org.omg.sysml.metamodel.Element WHERE containingProject = :project", Element.class); - query.setParameter("project", project); - return query.getResultList(); - } + public Set findAllByCommit(Commit commit) { + return jpa.transact(em -> { + // TODO Commit is detached at this point. This ternary mitigates by requerying for the Commit in this transaction. A better solution would be moving transaction handling up to service layer (supported by general wisdom) and optionally migrating to using Play's @Transactional/JPAApi. Pros would include removal of repetitive transaction handling at the DAO layer and ability to interface with multiple DAOs in the same transaction (consistent view). Cons include increased temptation to keep transaction open for longer than needed, e.g. during JSON serialization due to the convenience of @Transactional (deprecated in >= 2.8.x), and the service, a higher level of abstraction, becoming aware of transactions. An alternative would be DAO-to-DAO calls (generally discouraged) and delegating to non-transactional versions of methods. + Commit c = em.contains(commit) ? commit : em.find(metamodelProvider.getImplementationClass(Commit.class), commit.getId()); + return streamFlattenedElements(c).peek(PROXY_RESOLVER).collect(Collectors.toSet()); + }); } @Override - public Optional findByProjectAndId(Project project, UUID id) { - try (Session session = jpa.getEntityManagerFactory().unwrap(SessionFactory.class).openSession()) { - Query query = session.createQuery("FROM org.omg.sysml.metamodel.Element WHERE identifier = :identifier AND containingProject = :project", Element.class); - query.setParameter("identifier", id); - query.setParameter("project", project); - try { - return Optional.of(query.getSingleResult()); - } catch (NoResultException e) { - return Optional.empty(); + public Optional findByCommitAndId(Commit commit, UUID id) { + return jpa.transact(em -> { + return queryCommitTree(em.contains(commit) ? commit : em.find(metamodelProvider.getImplementationClass(Commit.class), commit.getId()), c -> + c.getChanges().stream().filter(record -> record.getIdentity() != null && record.getIdentity().getId() != null && record.getData() instanceof Element).filter(record -> id.equals(record.getIdentity().getId())).map(record -> (Element) record.getData()).findAny(), + Optional::isPresent) + .values().stream().filter(Optional::isPresent).map(Optional::get).peek(PROXY_RESOLVER).findAny(); + }); + } + + protected Map queryCommitTree(Commit commit, Function query) { + return queryCommitTree(commit, query, t -> false); + } + + protected Map queryCommitTree(Commit commit, Function query, java.util.function.Predicate terminationCondition) { + Map results = new LinkedHashMap<>(); + Commit currentCommit = commit; + Set visitedCommits = new HashSet<>(); + while (currentCommit != null && !visitedCommits.contains(currentCommit)) { + T t = query.apply(currentCommit); + results.put(currentCommit, t); + if (terminationCondition.test(t)) { + break; } + visitedCommits.add(currentCommit); + currentCommit = currentCommit.getPreviousCommit(); } + return results; + } + + protected Stream streamFlattenedElements(Commit commit) { + Set visitedElements = new HashSet<>(); + Map> results = queryCommitTree(commit, + c -> c.getChanges().stream().filter(record -> record.getIdentity() != null && record.getIdentity().getId() != null && record.getData() instanceof Element).filter(record -> !visitedElements.contains(record.getIdentity().getId())).peek(record -> visitedElements.add(record.getIdentity().getId())).map(record -> (Element) record.getData())); + return results.values().stream().flatMap(Function.identity()); } private Expression getTypeExpression(CriteriaBuilder builder, Root root) { diff --git a/app/dao/impl/jpa/JpaProjectDao.java b/app/dao/impl/jpa/JpaProjectDao.java index b129dc5d..cff90d5e 100644 --- a/app/dao/impl/jpa/JpaProjectDao.java +++ b/app/dao/impl/jpa/JpaProjectDao.java @@ -2,9 +2,9 @@ import dao.ProjectDao; import jpa.manager.JPAManager; -import org.omg.sysml.extension.Project; -import org.omg.sysml.extension.impl.ProjectImpl; -import org.omg.sysml.extension.impl.ProjectImpl_; +import org.omg.sysml.lifecycle.Project; +import org.omg.sysml.lifecycle.impl.ProjectImpl; +import org.omg.sysml.lifecycle.impl.ProjectImpl_; import javax.inject.Inject; import javax.inject.Singleton; @@ -33,7 +33,7 @@ public Optional findById(UUID id) { CriteriaBuilder builder = em.getCriteriaBuilder(); CriteriaQuery query = builder.createQuery(ProjectImpl.class); Root root = query.from(ProjectImpl.class); - query.select(root).where(builder.equal(root.get(ProjectImpl_.identifier), id)); + query.select(root).where(builder.equal(root.get(ProjectImpl_.id), id)); try { return Optional.of(em.createQuery(query).getSingleResult()); } catch (NoResultException e) { diff --git a/app/dao/impl/jpa/JpaRelationshipDao.java b/app/dao/impl/jpa/JpaRelationshipDao.java index 8bdca5e3..8d948772 100644 --- a/app/dao/impl/jpa/JpaRelationshipDao.java +++ b/app/dao/impl/jpa/JpaRelationshipDao.java @@ -1,11 +1,13 @@ package dao.impl.jpa; import config.MetamodelProvider; +import dao.ElementDao; import dao.RelationshipDao; import jpa.manager.JPAManager; import org.hibernate.Session; import org.hibernate.SessionFactory; -import org.omg.sysml.extension.Project; +import org.omg.sysml.lifecycle.Commit; +import org.omg.sysml.lifecycle.Project; import org.omg.sysml.metamodel.Element; import org.omg.sysml.metamodel.Relationship; import org.omg.sysml.metamodel.impl.MofObjectImpl; @@ -15,10 +17,7 @@ import javax.inject.Singleton; import javax.persistence.NoResultException; import javax.persistence.criteria.*; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -30,6 +29,8 @@ public class JpaRelationshipDao extends JpaDao implements Relation @Inject private JPAManager jpa; + private JpaElementDao elementDao = new JpaElementDao(); + @Override protected JPAManager getJpaManager() { return jpa; @@ -42,7 +43,7 @@ public Optional findById(UUID id) { CriteriaQuery query = builder.createQuery(MofObjectImpl.class); Root root = query.from(MofObjectImpl.class); query.select(root).where(builder.and( - builder.equal(root.get(MofObjectImpl_.identifier), id), + builder.equal(root.get(MofObjectImpl_.id), id), getTypeExpression(builder, root) )); try { @@ -75,104 +76,15 @@ public void deleteAll() { }); } - // TODO Change to use relatedElement when derived attributes are implemented - // TODO Implement. The @ManyToAny (polymorphism plus OneToMany in conjunction) of source and target makes this surprisingly difficult. Deferring to after versioning concept is introduced as that will likely break anything we implement now. - @Override - public List findAllByRelatedElement(Element element) { - /*try (Session session = jpa.getEntityManagerFactory().unwrap(SessionFactory.class).openSession()) { - Query query = session.createQuery("SELECT relationship FROM org.omg.sysml.metamodel.Relationship AS relationship JOIN relationship.source AS s JOIN org.omg.sysml.metamodel.Element AS ss ON s.identifier = ss.identifier WHERE ss.identifier = :elementIdentifier", Relationship.class); - Query query = session.createQuery("FROM org.omg.sysml.metamodel.Relationship", Relationship.class); - query.setParameter("elementIdentifier", element.getIdentifier()); - return query.getResultList(); - - CriteriaBuilder builder = session.getCriteriaBuilder(); - CriteriaQuery query = builder.createQuery(Relationship.class); - Root root = query.from(Relationship.class); - query.select(root).where( - builder.or( - builder.isMember(element, root.get("source")), - builder.isMember(element, root.get("target")) - ) - ); - return session.createQuery(query).getResultList(); - - - // Iterate types and SQL (union *_source and *_target, join mofobject and typetable) - /eturn getTypeStream().map(type -> { - System.out.println("FOO " + type.getName()); - CriteriaBuilder builder = session.getCriteriaBuilder(); - CriteriaQuery query = (CriteriaQuery) builder.createQuery(type); - Root root = (Root) query.from(type); - query.select(root).where( - builder.or( - builder.isMember(element.getIdentifier(), root.get("source").get(MofObjectImpl_.IDENTIFIER)), - builder.isMember(element.getIdentifier(), root.get("target").get(MofObjectImpl_.IDENTIFIER)) - ) - ); - return session.createQuery(query).getResultList(); - }).flatMap(Collection::stream).collect(Collectors.toList()); - } - - return jpa.transact(em -> { - CriteriaBuilder builder = em.getCriteriaBuilder(); - CriteriaQuery query = builder.createQuery(Relationship.class); - Root root = query.from(RelationshipImpl.class); - query.select(root).where( - builder.or( - builder.isMember(element, root.get(RelationshipImpl_.source)), - builder.isMember(element, root.get(RelationshipImpl_.target)) - ) - ); - return em.createQuery(query).getResultList(); - }); - - return jpa.transact(em -> { - Query query = em.createNativeQuery("SELECT CAST(Ownership.identifier AS text) FROM Ownership_source UNION INNER JOIN Ownership ON (Ownership.identifier = Ownership_source.OwnershipId) WHERE Ownership_source.sourceId = :elementIdentifier"); - query.setParameter("elementIdentifier", element.getIdentifier()); - List relationships = query.getResultList(); - System.out.println(relationships); - return Collections.emptyList(); - //return query.getResultList(); - }); - */ - return Collections.emptyList(); - } - - @Override - public List findAllBySourceElement(Element element) { - /* - return jpa.transact(em -> { - CriteriaBuilder builder = em.getCriteriaBuilder(); - CriteriaQuery query = builder.createQuery(Relationship.class); - Root root = query.from(RelationshipImpl.class); - query.select(root).where(builder.isMember(element, root.get(RelationshipImpl_.source))); - return em.createQuery(query).getResultList(); - }); - */ - return Collections.emptyList(); - } - @Override - public List findAllByTargetElement(Element element) { - /* + public Set findAllByCommitRelatedElement(Commit commit, Element relatedElement) { return jpa.transact(em -> { - CriteriaBuilder builder = em.getCriteriaBuilder(); - CriteriaQuery query = builder.createQuery(Relationship.class); - Root root = query.from(RelationshipImpl.class); - query.select(root).where(builder.isMember(element, root.get(RelationshipImpl_.target))); - return em.createQuery(query).getResultList(); + // TODO Commit is detached at this point. This ternary mitigates by requerying for the Commit in this transaction. A better solution would be moving transaction handling up to service layer (supported by general wisdom) and optionally migrating to using Play's @Transactional/JPAApi. Pros would include removal of repetitive transaction handling at the DAO layer and ability to interface with multiple DAOs in the same transaction (consistent view). Cons include increased temptation to keep transaction open for longer than needed, e.g. during JSON serialization due to the convenience of @Transactional (deprecated in >= 2.8.x), and the service, a higher level of abstraction, becoming aware of transactions. An alternative would be DAO-to-DAO calls (generally discouraged) and delegating to non-transactional versions of methods. + Commit c = em.contains(commit) ? commit : em.find(metamodelProvider.getImplementationClass(Commit.class), commit.getId()); + return elementDao.streamFlattenedElements(c).peek(System.out::println).filter(e -> e instanceof Relationship).map(e -> (Relationship) e) + .filter(relationship -> Stream.concat(relationship.getSource().stream(), relationship.getTarget().stream()).map(Element::getIdentifier).anyMatch(id -> id.equals(relatedElement.getIdentifier()))) + .collect(Collectors.toSet()); }); - */ - return Collections.emptyList(); - } - - @Override - public List findAllByProject(Project project) { - try (Session session = jpa.getEntityManagerFactory().unwrap(SessionFactory.class).openSession()) { - org.hibernate.query.Query query = session.createQuery("FROM org.omg.sysml.metamodel.Relationship WHERE containingProject = :project", Relationship.class); - query.setParameter("project", project); - return query.getResultList(); - } } private Stream> getTypeStream() { diff --git a/app/jackson/JacksonHelper.java b/app/jackson/JacksonHelper.java index 864ecc6b..240c91bc 100644 --- a/app/jackson/JacksonHelper.java +++ b/app/jackson/JacksonHelper.java @@ -2,17 +2,23 @@ import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectWriter; import play.libs.Json; import java.io.IOException; import java.util.Collection; +import java.util.function.Function; public class JacksonHelper { - // Java type erasure necessitates explicitly specifying JavaType for Collections. See https://github.com/FasterXML/jackson-databind/issues/23#issuecomment-6251193. public static JsonNode collectionValueToTree(@SuppressWarnings("rawtypes") Class collectionClass, Class elementClass, Collection collection) { + return collectionValueToTree(collectionClass, elementClass, collection, Function.identity()); + } + + // Java type erasure necessitates explicitly specifying JavaType for Collections. See https://github.com/FasterXML/jackson-databind/issues/23#issuecomment-6251193. + public static JsonNode collectionValueToTree(@SuppressWarnings("rawtypes") Class collectionClass, Class elementClass, Collection collection, Function objectWriterFunction) { JavaType javaType = Json.mapper().getTypeFactory().constructCollectionType(collectionClass, elementClass); try { - return Json.mapper().readerFor(javaType).readTree(Json.mapper().writerFor(javaType).writeValueAsString(collection)); + return Json.mapper().readerFor(javaType).readTree(objectWriterFunction.apply(Json.mapper().writerFor(javaType)).writeValueAsString(collection)); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/app/jackson/JpaIdentityDeserializer.java b/app/jackson/JpaIdentityDeserializer.java new file mode 100644 index 00000000..51435b48 --- /dev/null +++ b/app/jackson/JpaIdentityDeserializer.java @@ -0,0 +1,54 @@ +package jackson; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; + +import javax.persistence.EntityManager; +import java.io.IOException; + +public abstract class JpaIdentityDeserializer extends StdDeserializer { + private EntityManager entityManager; + + public JpaIdentityDeserializer(EntityManager entityManager) { + super((Class) null); + this.entityManager = entityManager; + } + + public JpaIdentityDeserializer() { + this(null); + } + + protected abstract boolean isIdentityField(String field); + + protected abstract T deserializeFromIdentity(JsonParser parser) throws IOException; + + @Override + public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentToken() != JsonToken.START_OBJECT) { + throw new JsonParseException(p, "Expected START_OBJECT. Received " + p.getCurrentName() + "."); + } + JsonToken token; + while ((token = p.nextToken()) != null && token != JsonToken.END_OBJECT) { + if (token == JsonToken.FIELD_NAME && isIdentityField(p.getCurrentName())) { + if (p.nextToken() == null) { + throw new JsonParseException(p, "No value for identity field."); + } + return deserializeFromIdentity(p); + } + } + return null; + } + + @Override + public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException { + return deserialize(p, ctxt); + } + + public EntityManager getEntityManager() { + return entityManager; + } +} \ No newline at end of file diff --git a/app/jackson/JpaIdentitySerializer.java b/app/jackson/JpaIdentitySerializer.java new file mode 100644 index 00000000..b06421ac --- /dev/null +++ b/app/jackson/JpaIdentitySerializer.java @@ -0,0 +1,35 @@ +package jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import org.omg.sysml.metamodel.MofObject; + +import javax.persistence.PersistenceException; +import java.io.IOException; + +public abstract class JpaIdentitySerializer extends StdSerializer { + public JpaIdentitySerializer() { + this(null); + } + + public JpaIdentitySerializer(Class clazz) { + super(clazz); + } + + protected abstract String getIdentityField(); + + protected abstract Object serializeToIdentity(T t); + + @Override + public void serialize(T value, JsonGenerator gen, SerializerProvider provider) throws IOException { + Object o = serializeToIdentity(value); + if (o == null) { + gen.writeNull(); + return; + } + gen.writeStartObject(); + gen.writeObjectField(getIdentityField(), o); + gen.writeEndObject(); + } +} \ No newline at end of file diff --git a/app/jackson/MofObjectDeserializer.java b/app/jackson/MofObjectDeserializer.java index 1ddd16ef..91f7fb6a 100644 --- a/app/jackson/MofObjectDeserializer.java +++ b/app/jackson/MofObjectDeserializer.java @@ -3,19 +3,35 @@ import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import javax.persistence.EntityManager; +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import org.omg.sysml.metamodel.impl.MofObjectImpl; + +import javax.persistence.EntityManager; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; -public class MofObjectDeserializer extends StdDeserializer { +public class MofObjectDeserializer extends StdDeserializer implements ContextualDeserializer { private EntityManager entityManager; + private JavaType type; + + //private static Map, Class> PROXY_MAP = new HashMap<>(); public MofObjectDeserializer(EntityManager entityManager) { + this(entityManager, null); + } + + public MofObjectDeserializer(EntityManager entityManager, JavaType type) { super((Class) null); this.entityManager = entityManager; + this.type = type; } public MofObjectDeserializer() { @@ -27,19 +43,25 @@ public MofObjectImpl deserialize(JsonParser p, DeserializationContext ctxt) thro if (p.currentToken() != JsonToken.START_OBJECT) { throw new JsonParseException(p, "Expected START_OBJECT. Received " + p.getCurrentName() + "."); } + + MofObjectImpl mof; +/* Class proxyClass = PROXY_MAP.computeIfAbsent(type.getRawClass(), clazz -> { + ProxyFactory factory = new ProxyFactory(); + factory.setSuperclass(clazz); + return factory.createClass(); + });*/ + try { + //mof = (MofObjectImpl) proxyClass.getConstructor().newInstance(); + mof = (MofObjectImpl) type.getRawClass().getConstructor().newInstance(); + } catch (ReflectiveOperationException e) { + throw new IOException(e); + } + JsonToken token; - MofObjectImpl mof = null; while ((token = p.nextToken()) != null && token != JsonToken.END_OBJECT) { - if (mof == null && token == JsonToken.FIELD_NAME && "identifier".equals(p.getCurrentName())) { + if (token == JsonToken.FIELD_NAME && "identifier".equals(p.getCurrentName())) { p.nextToken(); - Object id = p.getText(); - if ("java.util.UUID".endsWith("UUID")) { - id = java.util.UUID.fromString(id.toString()); - } - mof = entityManager.find(MofObjectImpl.class, id); - if (mof == null) { - throw new IOException("Unable to find an object with id " + id); - } + mof.setIdentifier(UUID.fromString(p.getText())); } } return mof; @@ -49,4 +71,11 @@ public MofObjectImpl deserialize(JsonParser p, DeserializationContext ctxt) thro public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException { return deserialize(p, ctxt); } + + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) { + //beanProperty is null when the type to deserialize is the top-level type or a generic type, not a type of a bean property + JavaType type = ctxt.getContextualType() != null ? ctxt.getContextualType() : property.getMember().getType(); + return new MofObjectDeserializer(entityManager, type); + } } \ No newline at end of file diff --git a/app/jackson/MofObjectSerializer.java b/app/jackson/MofObjectSerializer.java index cad61719..2179e52a 100644 --- a/app/jackson/MofObjectSerializer.java +++ b/app/jackson/MofObjectSerializer.java @@ -5,6 +5,8 @@ import com.fasterxml.jackson.databind.ser.std.StdSerializer; import javax.persistence.PersistenceException; import org.omg.sysml.metamodel.MofObject; +import org.omg.sysml.metamodel.impl.MofObjectImpl; + import java.io.IOException; public class MofObjectSerializer extends StdSerializer { @@ -19,7 +21,7 @@ public MofObjectSerializer(Class clazz) { @Override public void serialize(MofObject value, JsonGenerator gen, SerializerProvider provider) throws IOException { try { - if (value == null || value.getIdentifier() == null) { + if (value == null || value.getId() == null) { gen.writeNull(); return; } @@ -28,7 +30,12 @@ public void serialize(MofObject value, JsonGenerator gen, SerializerProvider pro return; } gen.writeStartObject(); - gen.writeObjectField("identifier", value.getIdentifier()); + // TODO Decide if @type and id should be exposed + // gen.writeObjectField("@type", value.getClass().getSimpleName()); + // gen.writeObjectField("id", value.getId()); + if (value instanceof MofObjectImpl) { + gen.writeObjectField("identifier", value.getIdentifier()); + } gen.writeEndObject(); } } \ No newline at end of file diff --git a/app/jackson/RecordSerialization.java b/app/jackson/RecordSerialization.java new file mode 100644 index 00000000..feedda74 --- /dev/null +++ b/app/jackson/RecordSerialization.java @@ -0,0 +1,87 @@ +package jackson; + +import com.fasterxml.jackson.core.JsonParser; +import org.omg.sysml.lifecycle.Commit; +import org.omg.sysml.lifecycle.Record; +import org.omg.sysml.lifecycle.impl.CommitImpl; +import org.omg.sysml.lifecycle.impl.ProjectImpl; +import org.omg.sysml.lifecycle.impl.RecordImpl; +import org.omg.sysml.lifecycle.impl.RecordImpl_; + +import javax.persistence.EntityManager; +import javax.persistence.EntityNotFoundException; +import java.io.IOException; +import java.util.UUID; + +public class RecordSerialization { + public static class RecordSerializer extends JpaIdentitySerializer { + + @Override + protected String getIdentityField() { + return RecordImpl_.ID; + } + + @Override + protected Object serializeToIdentity(Record record) { + return record != null ? record.getId() : null; + } + } + + public static abstract class RecordDeserializer extends JpaIdentityDeserializer { + public RecordDeserializer(EntityManager entityManager) { + super(entityManager); + } + + public RecordDeserializer() { + super(); + } + + protected abstract Class getRecordClass(); + + @Override + protected boolean isIdentityField(String field) { + return RecordImpl_.ID.equals(field); + } + + @Override + @SuppressWarnings("unchecked") + protected R deserializeFromIdentity(JsonParser parser) throws IOException { + UUID id = UUID.fromString(parser.getText()); + Record record = getEntityManager().find(getRecordClass(), id); + if (record == null) { + throw new IOException(new EntityNotFoundException("Record " + id + " not found.")); + } + return (R) record; + } + } + + public static class CommitDeserializer extends RecordDeserializer { + public CommitDeserializer(EntityManager entityManager) { + super(entityManager); + } + + public CommitDeserializer() { + super(); + } + + @Override + protected Class getRecordClass() { + return CommitImpl.class; + } + } + + public static class ProjectDeserializer extends RecordDeserializer { + public ProjectDeserializer(EntityManager entityManager) { + super(entityManager); + } + + public ProjectDeserializer() { + super(); + } + + @Override + protected Class getRecordClass() { + return ProjectImpl.class; + } + } +} diff --git a/app/jackson/databind/impl/HibernateObjectMapperFactory.java b/app/jackson/databind/impl/HibernateObjectMapperFactory.java index 7f14c77f..27379780 100644 --- a/app/jackson/databind/impl/HibernateObjectMapperFactory.java +++ b/app/jackson/databind/impl/HibernateObjectMapperFactory.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.module.SimpleAbstractTypeResolver; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.type.ClassKey; @@ -33,7 +34,8 @@ public HibernateObjectMapperFactory(MetamodelProvider metamodelProvider, JPAMana @Override public ObjectMapper getObjectMapper() { ObjectMapper objectMapper = Json.newDefaultMapper(); - objectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); + objectMapper.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); Hibernate5Module hibernate5Module = new Hibernate5Module(jpaManager.getEntityManagerFactory().unwrap(SessionFactory.class)); hibernate5Module.enable(Hibernate5Module.Feature.FORCE_LAZY_LOADING); diff --git a/app/javabean/JavaBeanHelper.java b/app/javabean/JavaBeanHelper.java new file mode 100644 index 00000000..b1a3e8f2 --- /dev/null +++ b/app/javabean/JavaBeanHelper.java @@ -0,0 +1,38 @@ +package javabean; + +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class JavaBeanHelper { + public static Map getBeanProperties(Object bean) { + try { + return Arrays.stream(Introspector.getBeanInfo(bean.getClass(), Object.class).getPropertyDescriptors()).collect(Collectors.toMap(PropertyDescriptor::getName, Function.identity())); + } catch (IntrospectionException e) { + throw new RuntimeException(e); + } + } + + // https://stackoverflow.com/a/8524043 + public static Map getBeanPropertyValues(Object bean) { + return getBeanProperties(bean).values().stream().filter(pd -> Objects.nonNull(pd.getReadMethod())).collect(HashMap::new, (map, pd) -> { + Object value = null; + try { + value = pd.getReadMethod().invoke(bean); + } catch (Exception ignored) { + } + map.put(pd.getName(), value); + }, Map::putAll); + } + + public static Map getBeanGettersSetters(Object bean) { + return getBeanProperties(bean).values().stream().filter(pd -> Objects.nonNull(pd.getReadMethod())).collect(HashMap::new, (map, pd) -> map.put(pd.getReadMethod(), pd.getWriteMethod()), Map::putAll); + } +} diff --git a/app/jpa/UseExistingOrGenerateUUIDGenerator.java b/app/jpa/UseExistingOrGenerateUUIDGenerator.java index 6bcfd714..9b0b7077 100644 --- a/app/jpa/UseExistingOrGenerateUUIDGenerator.java +++ b/app/jpa/UseExistingOrGenerateUUIDGenerator.java @@ -1,7 +1,6 @@ package jpa; import org.hibernate.HibernateException; -import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.UUIDGenerator; diff --git a/app/org/omg/sysml/extension/Project.java b/app/org/omg/sysml/extension/Project.java deleted file mode 100644 index daf66665..00000000 --- a/app/org/omg/sysml/extension/Project.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.omg.sysml.extension; - -import org.omg.sysml.metamodel.MofObject; - -public interface Project extends MofObject { - // Collection getContainedElement(); - String getName(); -} diff --git a/app/org/omg/sysml/extension/impl/ProjectImpl.java b/app/org/omg/sysml/extension/impl/ProjectImpl.java deleted file mode 100644 index be52cf3c..00000000 --- a/app/org/omg/sysml/extension/impl/ProjectImpl.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.omg.sysml.extension.impl; - -import com.fasterxml.jackson.annotation.*; -import org.omg.sysml.extension.Project; - -import org.hibernate.annotations.FetchMode; -import org.omg.sysml.metamodel.impl.MofObjectImpl; - -// import info.archinnov.achilles.annotations.UDT; - -import javax.persistence.Entity; -import javax.persistence.Lob; -import javax.persistence.DiscriminatorValue; -import javax.persistence.SecondaryTable; - -@Entity -@SecondaryTable(name = "Project") -@org.hibernate.annotations.Table(appliesTo = "Project", fetch = FetchMode.SELECT, optional = false) -// @info.archinnov.achilles.annotations.Table(table = "Project") -@DiscriminatorValue(value = "Project") -@JsonTypeName(value = "Project") -public class ProjectImpl extends MofObjectImpl implements Project { - private String name; - - @JsonProperty(required = true) - @JsonGetter - @Lob - @org.hibernate.annotations.Type(type = "org.hibernate.type.TextType") - @javax.persistence.Column(name = "name", table = "Project") - public String getName() { - return name; - } - - @JsonProperty(required = true) - @JsonSetter - public void setName(String name) { - this.name = name; - } - - @Override - @JsonIgnore - public Project getContainingProject() { - return null; - } - - /* - - // @info.archinnov.achilles.annotations.Column("ownedRelationship") - private Collection containedElement; - - @JsonProperty(required = true) - @JsonGetter - @JsonSerialize(contentUsing = MofObjectSerializer.class) - @ManyToAny(metaDef = "RelationshipMetaDef", metaColumn = @javax.persistence.Column(name = "ownedRelationshipType"), fetch = FetchType.LAZY) - @JoinTable(name = "Package_ownedRelationship", - joinColumns = @JoinColumn(name = "PackageId"), - inverseJoinColumns = @JoinColumn(name = "ownedRelationshipId")) - public Collection getContainedElement() { - if (containedElement == null) { - containedElement = new ArrayList<>(); - } - return containedElement; - } - - @JsonProperty(required = true) - @JsonSetter - @JsonDeserialize(contentUsing = MofObjectDeserializer.class, contentAs = RelationshipImpl.class) - public void setContainedElement(Collection containedElement) { - this.containedElement = containedElement; - } - - */ -} diff --git a/app/org/omg/sysml/extension/impl/package-info.java b/app/org/omg/sysml/extension/impl/package-info.java deleted file mode 100644 index b878e929..00000000 --- a/app/org/omg/sysml/extension/impl/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -@AnyMetaDefs(value = { - @AnyMetaDef(name = "ProjectMetaDef", metaType = "string", idType = "java.util.UUID", - metaValues = { - @MetaValue(value = "Project", targetEntity = ProjectImpl.class), - }), -}) - -// TODO Abstract this concept to cli option -@GenericGenerators(value = { - @GenericGenerator(name = "UseExistingOrGenerateUUIDGenerator", strategy = "jpa.UseExistingOrGenerateUUIDGenerator") -}) -package org.omg.sysml.extension.impl; - -import org.hibernate.annotations.AnyMetaDef; -import org.hibernate.annotations.AnyMetaDefs; -import org.hibernate.annotations.MetaValue; - -// TODO Abstract this concept to cli option -import org.hibernate.annotations.GenericGenerators; -import org.hibernate.annotations.GenericGenerator; \ No newline at end of file diff --git a/app/org/omg/sysml/lifecycle/Commit.java b/app/org/omg/sysml/lifecycle/Commit.java new file mode 100644 index 00000000..0a3dd89b --- /dev/null +++ b/app/org/omg/sysml/lifecycle/Commit.java @@ -0,0 +1,22 @@ +package org.omg.sysml.lifecycle; + +import java.time.ZonedDateTime; +import java.util.Set; + +public interface Commit extends Record { + Project getContainingProject(); + + void setContainingProject(Project containingProject); + + Set getChanges(); + + void setChanges(Set changes); + + Commit getPreviousCommit(); + + void setPreviousCommit(Commit previousCommit); + + ZonedDateTime getTimestamp(); + + void setTimestamp(ZonedDateTime timestamp); +} diff --git a/app/org/omg/sysml/lifecycle/ElementIdentity.java b/app/org/omg/sysml/lifecycle/ElementIdentity.java new file mode 100644 index 00000000..487cc0f0 --- /dev/null +++ b/app/org/omg/sysml/lifecycle/ElementIdentity.java @@ -0,0 +1,4 @@ +package org.omg.sysml.lifecycle; + +public interface ElementIdentity extends Record { +} diff --git a/app/org/omg/sysml/lifecycle/ElementVersion.java b/app/org/omg/sysml/lifecycle/ElementVersion.java new file mode 100644 index 00000000..fc20a044 --- /dev/null +++ b/app/org/omg/sysml/lifecycle/ElementVersion.java @@ -0,0 +1,9 @@ +package org.omg.sysml.lifecycle; + +import org.omg.sysml.metamodel.MofObject; + +public interface ElementVersion extends Record { + MofObject getData(); + + ElementIdentity getIdentity(); +} diff --git a/app/org/omg/sysml/lifecycle/Project.java b/app/org/omg/sysml/lifecycle/Project.java new file mode 100644 index 00000000..41773333 --- /dev/null +++ b/app/org/omg/sysml/lifecycle/Project.java @@ -0,0 +1,6 @@ +package org.omg.sysml.lifecycle; + +public interface Project extends Record { + // Collection getContainedElement(); + String getName(); +} diff --git a/app/org/omg/sysml/lifecycle/Record.java b/app/org/omg/sysml/lifecycle/Record.java new file mode 100644 index 00000000..fcb5852a --- /dev/null +++ b/app/org/omg/sysml/lifecycle/Record.java @@ -0,0 +1,7 @@ +package org.omg.sysml.lifecycle; + +import java.util.UUID; + +public interface Record { + UUID getId(); +} diff --git a/app/org/omg/sysml/lifecycle/impl/CommitImpl.java b/app/org/omg/sysml/lifecycle/impl/CommitImpl.java new file mode 100644 index 00000000..6e18dd65 --- /dev/null +++ b/app/org/omg/sysml/lifecycle/impl/CommitImpl.java @@ -0,0 +1,89 @@ +package org.omg.sysml.lifecycle.impl; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonView; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import jackson.RecordSerialization; +import org.omg.sysml.lifecycle.Commit; +import org.omg.sysml.lifecycle.ElementVersion; +import org.omg.sysml.lifecycle.Project; + +import javax.persistence.*; +import java.time.ZonedDateTime; +import java.util.HashSet; +import java.util.Set; + +@Entity(name = "Commit") +public class CommitImpl extends RecordImpl implements Commit { + private Project containingProject; + private Set changes; + private ZonedDateTime timestamp; + private Commit previousCommit; + + @Override + @ManyToOne(targetEntity = ProjectImpl.class, fetch = FetchType.LAZY) + @JsonSerialize(as = ProjectImpl.class, using = RecordSerialization.RecordSerializer.class) + @JsonView(Views.Compact.class) + public Project getContainingProject() { + return containingProject; + } + + @JsonDeserialize(as = ProjectImpl.class, using = RecordSerialization.ProjectDeserializer.class) + public void setContainingProject(Project containingProject) { + this.containingProject = containingProject; + } + + @Override + @OneToMany(targetEntity = ElementVersionImpl.class, cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JsonView(Views.Complete.class) + public Set getChanges() { + if (changes == null) { + changes = new HashSet<>(); + } + return changes; + } + + @JsonDeserialize(contentAs = ElementVersionImpl.class) + public void setChanges(Set changes) { + this.changes = changes; + } + + @Override + @Column + @JsonView(Views.Compact.class) + public ZonedDateTime getTimestamp() { + return timestamp; + } + + public void setTimestamp(ZonedDateTime timestamp) { + this.timestamp = timestamp; + } + + @ManyToOne(targetEntity = CommitImpl.class, fetch = FetchType.LAZY) + @JsonSerialize(as = CommitImpl.class, using = RecordSerialization.RecordSerializer.class) + @JsonView(Views.Compact.class) + public Commit getPreviousCommit() { + return previousCommit; + } + + @JsonDeserialize(as = CommitImpl.class, using = RecordSerialization.CommitDeserializer.class) + public void setPreviousCommit(Commit previousCommit) { + this.previousCommit = previousCommit; + } + + @Transient + @JsonProperty("@type") + @JsonView(Views.Compact.class) + public String getType() { + return Commit.class.getSimpleName(); + } + + public static class Views { + public interface Compact { + } + + public interface Complete extends Compact { + } + } +} diff --git a/app/org/omg/sysml/lifecycle/impl/ElementIdentityImpl.java b/app/org/omg/sysml/lifecycle/impl/ElementIdentityImpl.java new file mode 100644 index 00000000..3cfd9329 --- /dev/null +++ b/app/org/omg/sysml/lifecycle/impl/ElementIdentityImpl.java @@ -0,0 +1,18 @@ +package org.omg.sysml.lifecycle.impl; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import org.omg.sysml.lifecycle.ElementIdentity; + +import javax.persistence.Entity; +import javax.persistence.Transient; + +@Entity(name = "ElementIdentity") +@JsonTypeName(value = "ElementIdentity") +public class ElementIdentityImpl extends RecordImpl implements ElementIdentity { + @Transient + @JsonProperty("@type") + public String getType() { + return ElementIdentity.class.getSimpleName(); + } +} diff --git a/app/org/omg/sysml/lifecycle/impl/ElementVersionImpl.java b/app/org/omg/sysml/lifecycle/impl/ElementVersionImpl.java new file mode 100644 index 00000000..f598c35f --- /dev/null +++ b/app/org/omg/sysml/lifecycle/impl/ElementVersionImpl.java @@ -0,0 +1,49 @@ +package org.omg.sysml.lifecycle.impl; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.hibernate.annotations.Any; +import org.omg.sysml.lifecycle.ElementVersion; +import org.omg.sysml.metamodel.MofObject; +import org.omg.sysml.metamodel.impl.MofObjectImpl; +import org.omg.sysml.lifecycle.ElementIdentity; + +import javax.persistence.*; + +@Entity(name = "ElementVersion") +@JsonTypeName(value = "ElementVersion") +public class ElementVersionImpl extends RecordImpl implements ElementVersion { + private MofObject data; + private ElementIdentity identity; + + @Any(metaDef = "MofObjectMetaDef", metaColumn = @javax.persistence.Column(name = "dataType"), fetch = FetchType.EAGER) + @JoinColumn(name = "dataId") + @org.hibernate.annotations.Cascade(value = org.hibernate.annotations.CascadeType.ALL) + @JsonDeserialize(as = MofObjectImpl.class) + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME) + public MofObject getData() { + return data; + } + + public void setData(MofObject data) { + this.data = data; + } + + @ManyToOne(targetEntity = ElementIdentityImpl.class, cascade = javax.persistence.CascadeType.ALL, fetch = FetchType.EAGER) + @JsonDeserialize(as = ElementIdentityImpl.class) + public ElementIdentity getIdentity() { + return identity; + } + + public void setIdentity(ElementIdentity identity) { + this.identity = identity; + } + + @Transient + @JsonProperty("@type") + public static String getType() { + return ElementVersion.class.getSimpleName(); + } +} diff --git a/app/org/omg/sysml/lifecycle/impl/ProjectImpl.java b/app/org/omg/sysml/lifecycle/impl/ProjectImpl.java new file mode 100644 index 00000000..1c1d566b --- /dev/null +++ b/app/org/omg/sysml/lifecycle/impl/ProjectImpl.java @@ -0,0 +1,37 @@ +package org.omg.sysml.lifecycle.impl; + +import com.fasterxml.jackson.annotation.*; +import org.hibernate.annotations.FetchMode; +import org.omg.sysml.lifecycle.Commit; +import org.omg.sysml.lifecycle.Project; +import org.omg.sysml.metamodel.impl.MofObjectImpl; + +import javax.persistence.*; + +// import info.archinnov.achilles.annotations.UDT; + +@Entity(name = "Project") +public class ProjectImpl extends RecordImpl implements Project { + private String name; + + @JsonProperty(required = true) + @JsonGetter + @Lob + @org.hibernate.annotations.Type(type = "org.hibernate.type.TextType") + @javax.persistence.Column(name = "name", table = "Project") + public String getName() { + return name; + } + + @JsonProperty(required = true) + @JsonSetter + public void setName(String name) { + this.name = name; + } + + @Transient + @JsonProperty("@type") + public String getType() { + return Project.class.getSimpleName(); + } +} diff --git a/app/org/omg/sysml/lifecycle/impl/RecordImpl.java b/app/org/omg/sysml/lifecycle/impl/RecordImpl.java new file mode 100644 index 00000000..797908ff --- /dev/null +++ b/app/org/omg/sysml/lifecycle/impl/RecordImpl.java @@ -0,0 +1,24 @@ +package org.omg.sysml.lifecycle.impl; + +import org.omg.sysml.lifecycle.Record; + +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import java.util.UUID; + +@MappedSuperclass +public abstract class RecordImpl implements Record { + private UUID id; + + @Override + @Id + @GeneratedValue(generator = "UseExistingOrGenerateUUIDGenerator") + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } +} diff --git a/app/org/omg/sysml/lifecycle/impl/package-info.java b/app/org/omg/sysml/lifecycle/impl/package-info.java new file mode 100644 index 00000000..423ef04e --- /dev/null +++ b/app/org/omg/sysml/lifecycle/impl/package-info.java @@ -0,0 +1,21 @@ +@AnyMetaDefs(value = { + @AnyMetaDef(name = "ProjectMetaDef", metaType = "string", idType = "java.util.UUID", + metaValues = { + @MetaValue(value = "Project", targetEntity = ProjectImpl.class), + }), + @AnyMetaDef(name = "RecordMetaDef", metaType = "string", idType = "java.util.UUID", + metaValues = { + @MetaValue(value = "Commit", targetEntity = CommitImpl.class), + @MetaValue(value = "ElementIdentity", targetEntity = ElementIdentityImpl.class), + @MetaValue(value = "ElementVersion", targetEntity = ElementVersionImpl.class), + @MetaValue(value = "Project", targetEntity = ProjectImpl.class), + @MetaValue(value = "Record", targetEntity = RecordImpl.class), + }), +}) + +@GenericGenerators(value = { + @GenericGenerator(name = "UseExistingOrGenerateUUIDGenerator", strategy = "jpa.UseExistingOrGenerateUUIDGenerator") +}) +package org.omg.sysml.lifecycle.impl; + +import org.hibernate.annotations.*; \ No newline at end of file diff --git a/app/org/omg/sysml/metamodel/MofObject.java b/app/org/omg/sysml/metamodel/MofObject.java index ae826067..755a7b49 100644 --- a/app/org/omg/sysml/metamodel/MofObject.java +++ b/app/org/omg/sysml/metamodel/MofObject.java @@ -1,9 +1,7 @@ package org.omg.sysml.metamodel; public interface MofObject { - java.util.UUID getIdentifier(); - - // TODO Remove temporary modification for prototyping Project concept + java.util.UUID getId(); - org.omg.sysml.extension.Project getContainingProject(); + java.util.UUID getIdentifier(); } \ No newline at end of file diff --git a/app/org/omg/sysml/metamodel/impl/MofObjectImpl.java b/app/org/omg/sysml/metamodel/impl/MofObjectImpl.java index dff2b241..68d886a1 100644 --- a/app/org/omg/sysml/metamodel/impl/MofObjectImpl.java +++ b/app/org/omg/sysml/metamodel/impl/MofObjectImpl.java @@ -8,14 +8,6 @@ import javax.persistence.*; -// TODO Remove temporary modification for prototyping Project concept - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import jackson.MofObjectDeserializer; -import jackson.MofObjectSerializer; -import org.hibernate.annotations.Any; - @Entity(name = "MofObjectImpl") @Table(name = "MofObject") @DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING) @@ -23,37 +15,35 @@ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME) public abstract class MofObjectImpl implements MofObject { //@PartitionKey - public java.util.UUID identifier; + public java.util.UUID id; @Id // TODO Abstract this concept to cli option @GeneratedValue(generator = "UseExistingOrGenerateUUIDGenerator") - @Column(name = "identifier") - @JsonGetter(value = "identifier") - public java.util.UUID getIdentifier() { - return identifier; + @Column(name = "id") + @JsonGetter(value = "id") + public java.util.UUID getId() { + return id; } - @JsonSetter(value = "identifier") - public void setIdentifier(java.util.UUID identifier) { - this.identifier = identifier; + @JsonSetter(value = "id") + public void setId(java.util.UUID id) { + this.id = id; } - // TODO Remove temporary modification for prototyping Project concept + // TODO Remove hardcoding for identifier - private org.omg.sysml.extension.Project containingProject; + // @info.archinnov.achilles.annotations.Column("identifier") + private java.util.UUID identifier; @JsonGetter - @JsonSerialize(using = MofObjectSerializer.class) - @Any(metaDef = "ProjectMetaDef", metaColumn = @javax.persistence.Column(name = "containingProjectType"), fetch = FetchType.LAZY) - @JoinColumn(name = "containingProjectId", table = "MofObject") - public org.omg.sysml.extension.Project getContainingProject() { - return containingProject; + @javax.persistence.Column(name = "identifier", table = "MofObject") + public java.util.UUID getIdentifier() { + return identifier; } @JsonSetter - @JsonDeserialize(using = MofObjectDeserializer.class, as = org.omg.sysml.extension.impl.ProjectImpl.class) - public void setContainingProject(org.omg.sysml.extension.Project containingProject) { - this.containingProject = containingProject; + public void setIdentifier(java.util.UUID identifier) { + this.identifier = identifier; } } diff --git a/app/services/CommitService.java b/app/services/CommitService.java new file mode 100644 index 00000000..15241d9a --- /dev/null +++ b/app/services/CommitService.java @@ -0,0 +1,50 @@ +package services; + +import dao.CommitDao; +import dao.ProjectDao; +import org.omg.sysml.lifecycle.Commit; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Singleton +public class CommitService { + @Inject + private CommitDao commitDao; + + @Inject + private ProjectDao projectDao; + + public List getAll() { + return commitDao.findAll(); + } + + public Optional getById(UUID id) { + return commitDao.findById(id); + } + + public List getByProjectId(UUID projectId) { + return projectDao.findById(projectId).map(commitDao::findAllByProject).orElse(Collections.emptyList()); + } + + public Optional getByProjectIdAndId(UUID projectId, UUID commitId) { + return projectDao.findById(projectId).flatMap(project -> commitDao.findByProjectAndId(project, commitId)); + } + + public Optional getHeadByProjectId(UUID projectId) { + return projectDao.findById(projectId).flatMap(commitDao::findHeadByProject); + } + + public Optional create(Commit commit) { + return commit.getId() != null ? commitDao.update(commit) : commitDao.persist(commit); + } + + public Optional create(UUID projectId, Commit commit) { + commit.setContainingProject(projectDao.findById(projectId).orElseThrow(() -> new IllegalArgumentException("Project " + projectId + " not found."))); + return create(commit); + } +} diff --git a/app/services/ElementService.java b/app/services/ElementService.java index 37ff4f32..8294b7fa 100644 --- a/app/services/ElementService.java +++ b/app/services/ElementService.java @@ -1,15 +1,13 @@ package services; +import dao.CommitDao; import dao.ElementDao; import dao.ProjectDao; import org.omg.sysml.metamodel.Element; import javax.inject.Inject; import javax.inject.Singleton; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.UUID; +import java.util.*; @Singleton public class ElementService { @@ -19,6 +17,9 @@ public class ElementService { @Inject private ProjectDao projectDao; + @Inject + private CommitDao commitDao; + public List getAll() { return elementDao.findAll(); } @@ -27,15 +28,23 @@ public Optional getById(UUID id) { return elementDao.findById(id); } - public List getByProjectId(UUID projectId) { - return projectDao.findById(projectId).map(m -> elementDao.findAllByProject(m)).orElse(Collections.emptyList()); + public Set getByCommitId(UUID commitId) { + return commitDao.findById(commitId).map(c -> elementDao.findAllByCommit(c)).orElse(Collections.emptySet()); } - public Optional getByProjectIdAndId(UUID projectId, UUID elementId) { - return projectDao.findById(projectId).flatMap(m -> elementDao.findByProjectAndId(m, elementId)); + public Optional getByCommitIdAndId(UUID commitId, UUID elementId) { + return commitDao.findById(commitId).flatMap(m -> elementDao.findByCommitAndId(m, elementId)); } public Optional create(Element element) { return element.getIdentifier() != null ? elementDao.update(element) : elementDao.persist(element); } + + public Set getElementsByProjectIdCommitId(UUID projectId, UUID commitId) { + return projectDao.findById(projectId).flatMap(project -> commitDao.findByProjectAndId(project, commitId)).map(commit -> elementDao.findAllByCommit(commit)).orElse(Collections.emptySet()); + } + + public Optional getElementsByProjectIdCommitIdElementId(UUID projectId, UUID commitId, UUID elementId) { + return projectDao.findById(projectId).flatMap(project -> commitDao.findByProjectAndId(project, commitId)).flatMap(commit -> elementDao.findByCommitAndId(commit, elementId)); + } } diff --git a/app/services/ProjectService.java b/app/services/ProjectService.java index 8c536226..1e03ff8e 100644 --- a/app/services/ProjectService.java +++ b/app/services/ProjectService.java @@ -1,7 +1,7 @@ package services; import dao.ProjectDao; -import org.omg.sysml.extension.Project; +import org.omg.sysml.lifecycle.Project; import javax.inject.Inject; import javax.inject.Singleton; @@ -29,6 +29,6 @@ public Optional getById(UUID id) { } public Optional create(Project project) { - return project.getIdentifier() != null ? dao.update(project) : dao.persist(project); + return project.getId() != null ? dao.update(project) : dao.persist(project); } } diff --git a/app/services/RelationshipService.java b/app/services/RelationshipService.java index 1eeca97a..aada4e38 100644 --- a/app/services/RelationshipService.java +++ b/app/services/RelationshipService.java @@ -1,9 +1,12 @@ package services; +import dao.CommitDao; import dao.ElementDao; import dao.ProjectDao; import dao.RelationshipDao; +import org.omg.sysml.lifecycle.Commit; +import org.omg.sysml.metamodel.Element; import org.omg.sysml.metamodel.Relationship; import javax.inject.Inject; @@ -21,6 +24,9 @@ public class RelationshipService { @Inject private ProjectDao projectDao; + @Inject + private CommitDao commitDao; + public List getAll() { return relationshipDao.findAll(); } @@ -33,19 +39,9 @@ public Optional create(Relationship relationship) { return relationship.getIdentifier() != null ? relationshipDao.update(relationship) : relationshipDao.persist(relationship); } - public List getByRelatedElementId(UUID elementId) { - return elementDao.findById(elementId).map(e -> relationshipDao.findAllByRelatedElement(e)).orElse(Collections.emptyList()); - } - - public List getBySourceElementId(UUID elementId) { - return elementDao.findById(elementId).map(e -> relationshipDao.findAllBySourceElement(e)).orElse(Collections.emptyList()); - } - - public List getByTargetElementId(UUID elementId) { - return elementDao.findById(elementId).map(e -> relationshipDao.findAllByTargetElement(e)).orElse(Collections.emptyList()); - } - - public List getByProjectId(UUID projectId) { - return projectDao.findById(projectId).map(m -> relationshipDao.findAllByProject(m)).orElse(Collections.emptyList()); + public Set getRelationshipsByProjectCommitRelatedElement(UUID projectId, UUID commitId, UUID relatedElementId) { + Commit commit = projectDao.findById(projectId).flatMap(project -> commitDao.findByProjectAndId(project, commitId)).orElseThrow(() -> new IllegalArgumentException("Commit " + commitId + " not found.")); + Element relatedElement = elementDao.findByCommitAndId(commit, relatedElementId).orElseThrow(() -> new IllegalArgumentException("Element " + relatedElementId + " not found.")); + return relationshipDao.findAllByCommitRelatedElement(commit, relatedElement); } } diff --git a/build.sbt b/build.sbt index 2b85202b..0c84c593 100644 --- a/build.sbt +++ b/build.sbt @@ -1,7 +1,7 @@ name := """SysML-v2-API-Services""" organization := "org.omg" -version := "2020-02" +version := "2020-03-rc1" javacOptions ++= Seq("-source", "11", "-target", "11", "-Xlint") diff --git a/conf/META-INF/persistence.xml b/conf/META-INF/persistence.xml index d13ffaa5..afb2e3cf 100644 --- a/conf/META-INF/persistence.xml +++ b/conf/META-INF/persistence.xml @@ -111,9 +111,13 @@ org.omg.sysml.metamodel.impl.ValuePropertyImpl org.omg.sysml.metamodel.impl.ValueTypeImpl - org.omg.sysml.extension.impl - org.omg.sysml.extension.impl.ProjectImpl - + org.omg.sysml.lifecycle.impl + org.omg.sysml.lifecycle.impl.CommitImpl + org.omg.sysml.lifecycle.impl.ElementIdentityImpl + org.omg.sysml.lifecycle.impl.ElementVersionImpl + org.omg.sysml.lifecycle.impl.ProjectImpl + org.omg.sysml.lifecycle.impl.RecordImpl + diff --git a/conf/application.conf b/conf/application.conf index 85ebe9d9..efb0452c 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -5,6 +5,7 @@ play.http.secret.key="whatever" play.modules.enabled += "play.modules.swagger.SwaggerModule" play.filters.headers.contentSecurityPolicy = null play.filters.disabled+=play.filters.csrf.CSRFFilter +play.http.errorHandler = play.api.http.HtmlOrJsonHttpErrorHandler # https://www.playframework.com/documentation/2.7.x/JavaJPA db.default.jndiName=DefaultDS diff --git a/conf/routes b/conf/routes index e389924b..195a31d9 100644 --- a/conf/routes +++ b/conf/routes @@ -3,33 +3,28 @@ # ~~~~ # An example controller showing a sample home page -GET / controllers.HomeController.index +GET / controllers.HomeController.index # Project endpoints -GET /projects controllers.ProjectController.all() -GET /projects/:id controllers.ProjectController.byId(id : String) -POST /projects controllers.ProjectController.create(request : Request) +GET /projects controllers.ProjectController.all() +POST /projects controllers.ProjectController.create(request : Request) +GET /projects/:projectId controllers.ProjectController.byId(projectId : java.util.UUID) + +# Commit endpoints +GET /projects/:projectId/commits controllers.CommitController.byProject(projectId : java.util.UUID) +POST /projects/:projectId/commits controllers.CommitController.createWithProjectId(projectId : java.util.UUID, request : Request) +GET /projects/:projectId/commits/:commitId controllers.CommitController.byProjectAndId(projectId : java.util.UUID, commitId : java.util.UUID) +GET /projects/:projectId/head controllers.CommitController.headByProject(projectId : java.util.UUID) # Element endpoints -GET /elements controllers.ElementController.all() -GET /elements/:id controllers.ElementController.byId(id : String) -GET /projects/:mid/elements/:id controllers.ElementController.byProjectAndId(id : String, mid : String) -GET /projects/:mid/elements controllers.ElementController.byProject(mid : String) -POST /elements controllers.ElementController.create(request : Request) +GET /projects/:projectId/commits/:commitId/elements controllers.ElementController.getElementsByProjectIdCommitId(projectId : java.util.UUID, commitId : java.util.UUID) +GET /projects/:projectId/commits/:commitId/elements/:elementId controllers.ElementController.getElementByProjectIdCommitIdElementId(projectId : java.util.UUID, commitId : java.util.UUID, elementId : java.util.UUID) # Relationship endpoints -GET /relationships/:id controllers.RelationshipController.byId(id : String) -# GET /relationship/element/:id controllers.RelationshipController.byRelatedElementId(id : String) -# GET /relationship/source/:id controllers.RelationshipController.bySourceElementId(id : String) -# GET /relationship/source/:id/type/:type controllers.RelationshipController.bySourceElementId(id : String) -# GET /relationship/target/:id controllers.RelationshipController.byTargetElementId(id : String) -# GET /relationship/target/:id/type/:type controllers.RelationshipController.byTargetElementId(id : String) -GET /relationships controllers.RelationshipController.all() -GET /projects/:mid/relationships controllers.RelationshipController.byProject(mid : String) -POST /relationships controllers.RelationshipController.create(request : Request) +GET /projects/:projectId/commits/:commitId/elements/:relatedElementId/relationships controllers.RelationshipController.getRelationshipsByProjectIdCommitIdRelatedElementId(projectId : java.util.UUID, commitId : java.util.UUID, relatedElementId : java.util.UUID) # Map static resources from the /public folder to the /assets URL path -GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) +GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) -GET /docs/ controllers.Assets.at(path="/public/swagger",file="index.html") -GET /docs/*file controllers.Assets.at(path="/public/swagger",file) \ No newline at end of file +GET /docs/ controllers.Assets.at(path="/public/swagger",file="index.html") +GET /docs/*file controllers.Assets.at(path="/public/swagger",file) \ No newline at end of file diff --git a/generated/org/omg/sysml/lifecycle/impl/CommitImpl_.java b/generated/org/omg/sysml/lifecycle/impl/CommitImpl_.java new file mode 100644 index 00000000..a8e9bdcc --- /dev/null +++ b/generated/org/omg/sysml/lifecycle/impl/CommitImpl_.java @@ -0,0 +1,24 @@ +package org.omg.sysml.lifecycle.impl; + +import java.time.ZonedDateTime; +import javax.annotation.processing.Generated; +import javax.persistence.metamodel.SetAttribute; +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; + +@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor") +@StaticMetamodel(CommitImpl.class) +public abstract class CommitImpl_ extends org.omg.sysml.lifecycle.impl.RecordImpl_ { + + public static volatile SetAttribute changes; + public static volatile SingularAttribute previousCommit; + public static volatile SingularAttribute containingProject; + public static volatile SingularAttribute timestamp; + + public static final String CHANGES = "changes"; + public static final String PREVIOUS_COMMIT = "previousCommit"; + public static final String CONTAINING_PROJECT = "containingProject"; + public static final String TIMESTAMP = "timestamp"; + +} + diff --git a/generated/org/omg/sysml/lifecycle/impl/ElementIdentityImpl_.java b/generated/org/omg/sysml/lifecycle/impl/ElementIdentityImpl_.java new file mode 100644 index 00000000..c5abfc2b --- /dev/null +++ b/generated/org/omg/sysml/lifecycle/impl/ElementIdentityImpl_.java @@ -0,0 +1,13 @@ +package org.omg.sysml.lifecycle.impl; + +import javax.annotation.processing.Generated; +import javax.persistence.metamodel.StaticMetamodel; + +@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor") +@StaticMetamodel(ElementIdentityImpl.class) +public abstract class ElementIdentityImpl_ extends org.omg.sysml.lifecycle.impl.RecordImpl_ { + + + +} + diff --git a/generated/org/omg/sysml/lifecycle/impl/ElementVersionImpl_.java b/generated/org/omg/sysml/lifecycle/impl/ElementVersionImpl_.java new file mode 100644 index 00000000..35bfac2e --- /dev/null +++ b/generated/org/omg/sysml/lifecycle/impl/ElementVersionImpl_.java @@ -0,0 +1,16 @@ +package org.omg.sysml.lifecycle.impl; + +import javax.annotation.processing.Generated; +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; + +@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor") +@StaticMetamodel(ElementVersionImpl.class) +public abstract class ElementVersionImpl_ extends org.omg.sysml.lifecycle.impl.RecordImpl_ { + + public static volatile SingularAttribute identity; + + public static final String IDENTITY = "identity"; + +} + diff --git a/generated/org/omg/sysml/extension/impl/ProjectImpl_.java b/generated/org/omg/sysml/lifecycle/impl/ProjectImpl_.java similarity index 75% rename from generated/org/omg/sysml/extension/impl/ProjectImpl_.java rename to generated/org/omg/sysml/lifecycle/impl/ProjectImpl_.java index 11af296e..5d0930e3 100644 --- a/generated/org/omg/sysml/extension/impl/ProjectImpl_.java +++ b/generated/org/omg/sysml/lifecycle/impl/ProjectImpl_.java @@ -1,4 +1,4 @@ -package org.omg.sysml.extension.impl; +package org.omg.sysml.lifecycle.impl; import javax.annotation.processing.Generated; import javax.persistence.metamodel.SingularAttribute; @@ -6,7 +6,7 @@ @Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor") @StaticMetamodel(ProjectImpl.class) -public abstract class ProjectImpl_ extends org.omg.sysml.metamodel.impl.MofObjectImpl_ { +public abstract class ProjectImpl_ extends org.omg.sysml.lifecycle.impl.RecordImpl_ { public static volatile SingularAttribute name; diff --git a/generated/org/omg/sysml/lifecycle/impl/RecordImpl_.java b/generated/org/omg/sysml/lifecycle/impl/RecordImpl_.java new file mode 100644 index 00000000..168e7b41 --- /dev/null +++ b/generated/org/omg/sysml/lifecycle/impl/RecordImpl_.java @@ -0,0 +1,17 @@ +package org.omg.sysml.lifecycle.impl; + +import java.util.UUID; +import javax.annotation.processing.Generated; +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; + +@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor") +@StaticMetamodel(RecordImpl.class) +public abstract class RecordImpl_ { + + public static volatile SingularAttribute id; + + public static final String ID = "id"; + +} + diff --git a/generated/org/omg/sysml/metamodel/impl/MofObjectImpl_.java b/generated/org/omg/sysml/metamodel/impl/MofObjectImpl_.java index 85188353..2d9a5f2b 100644 --- a/generated/org/omg/sysml/metamodel/impl/MofObjectImpl_.java +++ b/generated/org/omg/sysml/metamodel/impl/MofObjectImpl_.java @@ -10,8 +10,10 @@ public abstract class MofObjectImpl_ { public static volatile SingularAttribute identifier; + public static volatile SingularAttribute id; public static final String IDENTIFIER = "identifier"; + public static final String ID = "id"; } diff --git a/public/swagger/openapi.yaml b/public/swagger/openapi.yaml index e4622b45..f525573d 100644 --- a/public/swagger/openapi.yaml +++ b/public/swagger/openapi.yaml @@ -5,6 +5,7 @@ info: version: 1.0.0 tags: - name: Project + - name: Commit - name: Element - name: Relationship @@ -14,7 +15,7 @@ paths: tags: - Project operationId: getProjects - summary: Get all projects + summary: Get projects produces: - application/json responses: @@ -33,8 +34,8 @@ paths: post: tags: - Project - operationId: createProject - summary: Add a new project + operationId: postProject + summary: Create project consumes: - application/json produces: @@ -55,18 +56,19 @@ paths: $ref: '#/responses/InternalServerError' default: $ref: '#/responses/Default' - /projects/{identifier}: + /projects/{projectId}: parameters: - - name: identifier + - name: projectId in: path description: ID of the project type: string + format: uuid required: true get: tags: - Project - operationId: getProject - summary: Get project by its ID + operationId: getProjectById + summary: Get project by ID produces: - application/json responses: @@ -82,23 +84,28 @@ paths: $ref: '#/responses/InternalServerError' default: $ref: '#/responses/Default' - - - /elements: + /projects/{projectId}/commits: + parameters: + - name: projectId + in: path + description: ID of the project + type: string + format: uuid + required: true get: tags: - - Element - operationId: getElements - summary: Get all elements + - Commit + operationId: getCommitsByProject + summary: Get commits by project produces: - application/json responses: 200: description: Ok schema: - type: array - items: - $ref: '#/definitions/Element' + $ref: '#/definitions/Commit' + 404: + $ref: '#/responses/NotFound' 415: $ref: '#/responses/BadContentType' 500: @@ -107,9 +114,9 @@ paths: $ref: '#/responses/Default' post: tags: - - Element - operationId: createElement - summary: Add a new element + - Commit + operationId: postCommitByProject + summary: Create commit by project consumes: - application/json produces: @@ -119,37 +126,42 @@ paths: name: body required: true schema: - $ref: '#/definitions/Element' + $ref: '#/definitions/Commit' responses: 201: description: Created schema: - $ref: '#/definitions/Element' + $ref: '#/definitions/Commit' 415: $ref: '#/responses/BadContentType' 500: $ref: '#/responses/InternalServerError' default: $ref: '#/responses/Default' - /elements/{identifier}: + /projects/{projectId}/commits/{commitId}: parameters: - - name: identifier + - name: projectId in: path - description: ID of the element + description: ID of the project + type: string + required: true + - name: commitId + in: path + description: ID of the commit type: string required: true get: tags: - - Element - operationId: getElement - summary: Get element by its ID + - Commit + operationId: getCommitByProjectAndId + summary: Get commit by project and ID produces: - application/json responses: 200: description: Ok schema: - $ref: '#/definitions/Element' + $ref: '#/definitions/Commit' 404: $ref: '#/responses/NotFound' 415: @@ -158,30 +170,25 @@ paths: $ref: '#/responses/InternalServerError' default: $ref: '#/responses/Default' - /projects/{project_identifier}/elements/{element_identifier}: + /projects/{projectId}/head: parameters: - - name: project_identifier + - name: projectId in: path description: ID of the project type: string required: true - - name: element_identifier - in: path - description: ID of the element - type: string - required: true get: tags: - - Element - operationId: getElementByProjectAndId - summary: Get element by project ID and its ID + - Commit + operationId: getHeadCommitByProject + summary: Get head commit by project produces: - application/json responses: 200: description: Ok schema: - $ref: '#/definitions/Element' + $ref: '#/definitions/Commit' 404: $ref: '#/responses/NotFound' 415: @@ -190,18 +197,25 @@ paths: $ref: '#/responses/InternalServerError' default: $ref: '#/responses/Default' - /projects/{project_identifier}/elements: + /projects/{projectId}/commits/{commitId}/elements: parameters: - - name: project_identifier + - name: projectId in: path description: ID of the project type: string + format: uuid + required: true + - name: commitId + in: path + description: ID of the commit + type: string + format: uuid required: true get: tags: - Element - operationId: getElementsInProject - summary: Get all elements in the project + operationId: getElementsByProjectCommit + summary: Get elements by project and commit produces: - application/json responses: @@ -217,73 +231,38 @@ paths: $ref: '#/responses/InternalServerError' default: $ref: '#/responses/Default' - - /relationships: - get: - tags: - - Relationship - operationId: getRelationships - summary: Get all relationships - produces: - - application/json - responses: - 200: - description: Ok - schema: - type: array - items: - $ref: '#/definitions/Relationship' - 415: - $ref: '#/responses/BadContentType' - 500: - $ref: '#/responses/InternalServerError' - default: - $ref: '#/responses/Default' - post: - tags: - - Relationship - operationId: createRelationship - summary: Add a new relationship - consumes: - - application/json - produces: - - application/json - parameters: - - in: body - name: body - required: true - schema: - $ref: '#/definitions/Relationship' - responses: - 201: - description: Created - schema: - $ref: '#/definitions/Relationship' - 415: - $ref: '#/responses/BadContentType' - 500: - $ref: '#/responses/InternalServerError' - default: - $ref: '#/responses/Default' - /relationships/{identifier}: + /projects/{projectId}/commits/{commitId}/elements/{elementId}: parameters: - - name: identifier + - name: projectId in: path - description: ID of the relationship + description: ID of the project + type: string + format: uuid + required: true + - name: commitId + in: path + description: ID of the commit type: string + format: uuid + required: true + - name: elementId + in: path + description: ID of the element + type: string + format: uuid required: true get: tags: - - Relationship - operationId: getRelationship - summary: Get relationship by its ID + - Element + operationId: getElementByProjectCommitId + summary: Get element by project, commit and ID produces: - application/json responses: 200: description: Ok schema: - $ref: '#/definitions/Relationship' + $ref: '#/definitions/Element' 404: $ref: '#/responses/NotFound' 415: @@ -292,99 +271,31 @@ paths: $ref: '#/responses/InternalServerError' default: $ref: '#/responses/Default' - # /relationships/elements/{element_identifier}: - # parameters: - # - name: element_identifier - # in: path - # description: ID of the element that is the source or target of relationships - # type: string - # required: true - # get: - # tags: - # - Relationship - # operationId: getRelationshipsByElement - # summary: Get all relationships with the given element as either source or target - # produces: - # - application/json - # responses: - # 200: - # description: Ok - # schema: - # type: array - # items: - # $ref: '#/definitions/Relationship' - # 415: - # $ref: '#/responses/BadContentType' - # 500: - # $ref: '#/responses/InternalServerError' - # default: - # $ref: '#/responses/Default' - # /relationship/source/{source_identifier}: - # parameters: - # - name: source_identifier - # in: path - # description: ID of the element that is the source of relationships - # type: string - # required: true - # get: - # tags: - # - Relationship - # operationId: getRelationshipsBySource - # summary: Get all relationships with the given element as the source - # produces: - # - application/json - # responses: - # 200: - # description: Ok - # schema: - # type: array - # items: - # $ref: '#/definitions/Relationship' - # 415: - # $ref: '#/responses/BadContentType' - # 500: - # $ref: '#/responses/InternalServerError' - # default: - # $ref: '#/responses/Default' - # /relationship/target/{target_identifier}: - # parameters: - # - name: target_identifier - # in: path - # description: ID of the element that is the target of relationships - # type: string - # required: true - # get: - # tags: - # - Relationship - # operationId: getRelationshipsByTarget - # summary: Get all relationships with the given element as the target - # produces: - # - application/json - # responses: - # 200: - # description: Ok - # schema: - # type: array - # items: - # $ref: '#/definitions/Relationship' - # 415: - # $ref: '#/responses/BadContentType' - # 500: - # $ref: '#/responses/InternalServerError' - # default: - # $ref: '#/responses/Default' - /projects/{project_identifier}/relationships: + /projects/{projectId}/commits/{commitId}/elements/{relatedElementId}/relationships: parameters: - - name: project_identifier + - name: projectId in: path description: ID of the project type: string + format: uuid + required: true + - name: commitId + in: path + description: ID of the commit + type: string + format: uuid + required: true + - name: relatedElementId + in: path + description: ID of the related element + type: string + format: uuid required: true get: tags: - Relationship - operationId: getRelationshipsByProject - summary: Get all relationships in the project + operationId: getRelationshipsByProjectCommitRelatedElement + summary: Get relationships by project, commit, and related element. produces: - application/json responses: @@ -400,28 +311,74 @@ paths: $ref: '#/responses/InternalServerError' default: $ref: '#/responses/Default' - definitions: - Project: + Record: type: object properties: - '@type': - type: string - name: - type: string - identifier: + id: type: string format: uuid - Element: + Project: + type: object + allOf: + - $ref: '#/definitions/Record' + - properties: + '@type': + type: string + enum: + - 'Project' + name: + type: string + Commit: + type: object + allOf: + - $ref: '#/definitions/Record' + - properties: + '@type': + type: string + enum: + - 'Commit' + changes: + type: array + items: + $ref: '#/definitions/ElementVersion' + containingProject: + $ref: '#/definitions/Record' + ElementVersion: + type: object + allOf: + - $ref: '#/definitions/Record' + - properties: + '@type': + type: string + enum: + - 'ElementVersion' + data: + $ref: '#/definitions/Element' + identity: + $ref: '#/definitions/ElementIdentity' + ElementIdentity: + type: object + allOf: + - $ref: '#/definitions/Record' + - properties: + '@type': + type: string + enum: + - 'ElementIdentity' + Identified: type: object properties: - '@type': - type: string - containingProject: - $ref: '#/definitions/Identified' identifier: type: string format: uuid + Element: + type: object + allOf: + - $ref: '#/definitions/Identified' + - properties: + '@type': + type: string additionalProperties: type: object Relationship: @@ -437,12 +394,6 @@ definitions: type: array items: $ref: '#/definitions/Identified' - Identified: - type: object - properties: - identifier: - type: string - format: uuid Error: type: object properties: