From 95375541495a27ffa9ef87a8c583de23fa8cc332 Mon Sep 17 00:00:00 2001 From: Jens Pelzetter Date: Tue, 8 Dec 2020 20:26:13 +0100 Subject: [PATCH] Added a reload method to the interface of the AbstractImExporter class --- .../AbstractContentItemImExporter.java | 32 +++- .../ContentSectionImExporter.java | 15 ++ .../categorization/Categorization.java | 20 +-- .../CategorizationImExporter.java | 29 +++- .../categorization/CategoryImExporter.java | 15 ++ .../categorization/DomainImExporter.java | 27 +++- .../categorization/DomainOwnership.java | 12 +- .../DomainOwnershipImExporter.java | 31 +++- .../libreccm/core/ResourceTypeImExporter.java | 20 ++- .../imexport/AbstractEntityImExporter.java | 31 +++- .../libreccm/security/GroupImExporter.java | 32 ++-- .../libreccm/security/GroupMembership.java | 25 +++- .../security/GroupMembershipImExporter.java | 30 +++- .../security/PermissionImExporter.java | 29 ++-- .../org/libreccm/security/RoleImExporter.java | 55 ++++++- .../org/libreccm/security/RoleMembership.java | 19 ++- .../security/RoleMembershipImExporter.java | 29 +++- .../org/libreccm/security/UserImExporter.java | 21 ++- .../ui/admin/imexport/ExportTask.java | 71 +++++++++ .../ui/admin/imexport/ExportTaskStatus.java | 140 ++++++++++++++++++ ...xportTask.java => ImExportTaskStatus.java} | 15 +- .../imexport/ImExportTaskStatusEnum.java | 31 ++++ .../ui/admin/imexport/ImExportTasks.java | 49 ++++-- .../imexport/ImportExportTaskManager.java | 95 ++++++++---- .../ui/admin/imexport/ImportTask.java | 46 ++++++ .../ui/admin/imexport/ImportTaskStatus.java | 95 ++++++++++++ .../libreccm/web/ApplicationImExporter.java | 17 ++- .../workflow/AssignableTaskImExporter.java | 19 ++- .../org/libreccm/workflow/TaskAssignment.java | 33 ++++- .../workflow/TaskAssignmentImExporter.java | 36 ++++- .../workflow/TaskCommentImExporter.java | 16 +- .../org/libreccm/workflow/TaskDependency.java | 26 +++- .../workflow/TaskDependencyImExporter.java | 33 ++++- .../libreccm/workflow/WorkflowImExporter.java | 19 ++- .../libreccm/ui/admin/imexport/imexport.xhtml | 17 +-- .../docrepo/BlobObjectImExporter.java | 19 ++- .../org/libreccm/docrepo/FileImExporter.java | 25 +++- .../libreccm/docrepo/FolderImExporter.java | 19 ++- .../docrepo/RepositoryImExporter.java | 23 ++- 39 files changed, 1107 insertions(+), 209 deletions(-) create mode 100644 ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ExportTask.java create mode 100644 ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ExportTaskStatus.java rename ccm-core/src/main/java/org/libreccm/ui/admin/imexport/{ImExportTask.java => ImExportTaskStatus.java} (88%) create mode 100644 ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImExportTaskStatusEnum.java create mode 100644 ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImportTask.java create mode 100644 ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImportTaskStatus.java diff --git a/ccm-cms/src/main/java/org/librecms/contentsection/AbstractContentItemImExporter.java b/ccm-cms/src/main/java/org/librecms/contentsection/AbstractContentItemImExporter.java index 2de70e2c1..6098f25c8 100644 --- a/ccm-cms/src/main/java/org/librecms/contentsection/AbstractContentItemImExporter.java +++ b/ccm-cms/src/main/java/org/librecms/contentsection/AbstractContentItemImExporter.java @@ -10,6 +10,7 @@ import org.libreccm.imexport.AbstractEntityImExporter; import org.libreccm.imexport.Exportable; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import javax.inject.Inject; @@ -20,26 +21,41 @@ import javax.transaction.Transactional; * @author Jens Pelzetter * @param */ -public abstract class AbstractContentItemImExporter +public abstract class AbstractContentItemImExporter extends AbstractEntityImExporter { - + @Inject - private ContentItemRepository itemRepository; - + private ContentItemRepository itemRepository; + @Override protected Set> getRequiredEntities() { - + final Set> entities = new HashSet<>(); entities.add(Category.class); entities.add(ContentSection.class); - + return entities; } - + @Override @Transactional(Transactional.TxType.REQUIRED) public void saveImportedEntity(final T entity) { itemRepository.save(entity); } - + + @Override + protected T reloadEntity(final T entity) { + return itemRepository + .findById( + Objects.requireNonNull(entity).getObjectId(), getEntityClass() + ).orElseThrow( + () -> new IllegalArgumentException( + String.format( + "ContentItem entity %s not found in database.", + Objects.toString(entity) + ) + ) + ); + } + } diff --git a/ccm-cms/src/main/java/org/librecms/contentsection/ContentSectionImExporter.java b/ccm-cms/src/main/java/org/librecms/contentsection/ContentSectionImExporter.java index a2cf7ecd0..d4405055c 100644 --- a/ccm-cms/src/main/java/org/librecms/contentsection/ContentSectionImExporter.java +++ b/ccm-cms/src/main/java/org/librecms/contentsection/ContentSectionImExporter.java @@ -10,6 +10,7 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.Collections; +import java.util.Objects; import java.util.Set; import javax.enterprise.context.RequestScoped; @@ -44,4 +45,18 @@ public class ContentSectionImExporter sectionRepository.save(entity); } + @Override + protected ContentSection reloadEntity(final ContentSection entity) { + return sectionRepository + .findById(Objects.requireNonNull(entity).getObjectId()) + .orElseThrow( + () -> new IllegalArgumentException( + String.format( + "ContentSection entity %s not found in database.", + Objects.toString(entity) + ) + ) + ); + } + } diff --git a/ccm-core/src/main/java/org/libreccm/categorization/Categorization.java b/ccm-core/src/main/java/org/libreccm/categorization/Categorization.java index 5b8b0f528..f5556b22f 100644 --- a/ccm-core/src/main/java/org/libreccm/categorization/Categorization.java +++ b/ccm-core/src/main/java/org/libreccm/categorization/Categorization.java @@ -54,6 +54,11 @@ import javax.persistence.Table; @Entity @Table(name = "CATEGORIZATIONS", schema = DB_SCHEMA) @NamedQueries({ + @NamedQuery( + name = "Categorization.findById", + query + = "SELECT c FROM Categorization c WHERE c.categorizationId = :categorizationId" + ), @NamedQuery( name = "Categorization.findByUuid", query = "SELECT c FROM Categorization c WHERE c.uuid = :uuid" @@ -62,36 +67,31 @@ import javax.persistence.Table; name = "Categorization.find", query = "SELECT c FROM Categorization c " + "WHERE c.category = :category " - + "AND c.categorizedObject = :object") - , + + "AND c.categorizedObject = :object"), @NamedQuery( name = "Categorization.isAssignedTo", query = "SELECT (CASE WHEN COUNT(c) > 0 THEN true ELSE false END) " + "FROM Categorization c " + "WHERE c.category = :category " - + "AND c.categorizedObject = :object") - , + + "AND c.categorizedObject = :object"), @NamedQuery( name = "Categorization.isAssignedToWithType", query = "SELECT (CASE WHEN COUNT(c) > 0 THEN true ELSE false END) " + "FROM Categorization c " + "WHERE c.category = :category " + "AND c.categorizedObject = :object " - + "AND c.type = :type") - , + + "AND c.type = :type"), @NamedQuery( name = "Categorization.findIndexObject", query = "SELECT c.categorizedObject FROM Categorization c " + "WHERE c.category = :category " - + "AND c.indexObject = TRUE") - , + + "AND c.indexObject = TRUE"), @NamedQuery( name = "Categorization.findIndexObjectCategorization", query = "SELECT c FROM Categorization c " + "WHERE c.category = :category " + "AND c.indexObject = TRUE" - ) - , + ), @NamedQuery( name = "Categorization.hasIndexObject", query = "SELECT (CASE WHEN COUNT(c.categorizedObject) > 0 THEN true " diff --git a/ccm-core/src/main/java/org/libreccm/categorization/CategorizationImExporter.java b/ccm-core/src/main/java/org/libreccm/categorization/CategorizationImExporter.java index 45b6a74b1..aa7e64555 100644 --- a/ccm-core/src/main/java/org/libreccm/categorization/CategorizationImExporter.java +++ b/ccm-core/src/main/java/org/libreccm/categorization/CategorizationImExporter.java @@ -23,17 +23,20 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import javax.enterprise.context.RequestScoped; import javax.enterprise.inject.Instance; import javax.inject.Inject; import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import javax.persistence.TypedQuery; import javax.transaction.Transactional; /** * Exporter/Importer for {@link Categorization} entities. - * + * * @author Jens Pelzetter */ @RequestScoped @@ -54,10 +57,9 @@ public class CategorizationImExporter @Override protected Set> getRequiredEntities() { - final Set> entities = new HashSet<>(); entities.add(Category.class); - + dependenciesProviders.forEach( provider -> entities.addAll(provider.getCategorizableEntities()) ); @@ -68,8 +70,27 @@ public class CategorizationImExporter @Override @Transactional(Transactional.TxType.REQUIRED) protected void saveImportedEntity(final Categorization entity) { - entityManager.persist(entity); } + @Override + protected Categorization reloadEntity(final Categorization entity) { + try { + return entityManager.createNamedQuery( + "Categorization.findById", + Categorization.class + ).setParameter( + "categorizationId", + Objects.requireNonNull(entity).getCategorizationId() + ).getSingleResult(); + } catch (NoResultException ex) { + throw new IllegalArgumentException( + String.format( + "Categorization entity %s was not found in the database.", + Objects.toString(entity) + ) + ); + } + } + } diff --git a/ccm-core/src/main/java/org/libreccm/categorization/CategoryImExporter.java b/ccm-core/src/main/java/org/libreccm/categorization/CategoryImExporter.java index 9012e6245..e851ce5e0 100644 --- a/ccm-core/src/main/java/org/libreccm/categorization/CategoryImExporter.java +++ b/ccm-core/src/main/java/org/libreccm/categorization/CategoryImExporter.java @@ -23,6 +23,7 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import javax.enterprise.context.RequestScoped; @@ -60,4 +61,18 @@ public class CategoryImExporter extends AbstractEntityImExporter { categoryRepository.save(entity); } + @Override + protected Category reloadEntity(final Category entity) { + return categoryRepository + .findById(Objects.requireNonNull(entity).getObjectId()) + .orElseThrow( + () -> new IllegalArgumentException( + String.format( + "Category entity %s does not exist in the database.", + Objects.toString(entity) + ) + ) + ); + } + } diff --git a/ccm-core/src/main/java/org/libreccm/categorization/DomainImExporter.java b/ccm-core/src/main/java/org/libreccm/categorization/DomainImExporter.java index 2dda0ebfc..33c475539 100644 --- a/ccm-core/src/main/java/org/libreccm/categorization/DomainImExporter.java +++ b/ccm-core/src/main/java/org/libreccm/categorization/DomainImExporter.java @@ -23,6 +23,7 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.Collections; +import java.util.Objects; import java.util.Set; import javax.enterprise.context.RequestScoped; @@ -30,7 +31,7 @@ import javax.inject.Inject; /** * Exporter/Importer for {@link Domain} entities. - * + * * @author Jens Pelzetter */ @RequestScoped @@ -39,10 +40,10 @@ public class DomainImExporter extends AbstractEntityImExporter { @Inject private DomainRepository domainRepository; - + @Override public Class getEntityClass() { - + return Domain.class; } @@ -54,10 +55,22 @@ public class DomainImExporter extends AbstractEntityImExporter { @Override protected Set> getRequiredEntities() { - + return Collections.emptySet(); } - - - + + @Override + protected Domain reloadEntity(final Domain entity) { + return domainRepository + .findById(Objects.requireNonNull(entity).getObjectId()) + .orElseThrow( + () -> new IllegalArgumentException( + String.format( + "Domain entity %s was not found in the database.", + Objects.toString(entity) + ) + ) + ); + } + } diff --git a/ccm-core/src/main/java/org/libreccm/categorization/DomainOwnership.java b/ccm-core/src/main/java/org/libreccm/categorization/DomainOwnership.java index db2a9ff65..09a77f170 100644 --- a/ccm-core/src/main/java/org/libreccm/categorization/DomainOwnership.java +++ b/ccm-core/src/main/java/org/libreccm/categorization/DomainOwnership.java @@ -54,11 +54,15 @@ import javax.xml.bind.annotation.XmlElement; @Entity @Table(name = "DOMAIN_OWNERSHIPS", schema = DB_SCHEMA) @NamedQueries({ + @NamedQuery( + name = "DomainOwnership.findById", + query + = "SELECT o FROM DomainOwnership o WHERE o.ownershipId = :ownershipId" + ), @NamedQuery( name = "DomainOwnership.findByUuid", query = "SELECT o FROM DomainOwnership o WHERE o.uuid = :uuid" - ) - , + ), @NamedQuery( name = "DomainOwnership.findByOwnerAndDomain", query = "SELECT o FROM DomainOwnership o " @@ -77,7 +81,7 @@ public class DomainOwnership implements Serializable, Exportable { @Column(name = "OWNERSHIP_ID") @GeneratedValue(strategy = GenerationType.AUTO) private long ownershipId; - + @Column(name = "UUID", unique = true, nullable = false) @XmlElement(name = "uuid", namespace = CoreConstants.CORE_XML_NS) private String uuid; @@ -132,7 +136,7 @@ public class DomainOwnership implements Serializable, Exportable { public void setUuid(final String uuid) { this.uuid = uuid; } - + public CcmApplication getOwner() { return owner; } diff --git a/ccm-core/src/main/java/org/libreccm/categorization/DomainOwnershipImExporter.java b/ccm-core/src/main/java/org/libreccm/categorization/DomainOwnershipImExporter.java index 1fda262bb..65dd84feb 100644 --- a/ccm-core/src/main/java/org/libreccm/categorization/DomainOwnershipImExporter.java +++ b/ccm-core/src/main/java/org/libreccm/categorization/DomainOwnershipImExporter.java @@ -24,17 +24,19 @@ import org.libreccm.imexport.Processes; import org.libreccm.web.CcmApplication; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.persistence.EntityManager; +import javax.persistence.NoResultException; import javax.transaction.Transactional; /** * Exporter/Importer for {@link DomainOwnership} entities. - * - * + * + * * @author Jens Pelzetter */ @RequestScoped @@ -47,20 +49,17 @@ public class DomainOwnershipImExporter @Override public Class getEntityClass() { - return DomainOwnership.class; } @Override @Transactional(Transactional.TxType.REQUIRED) protected void saveImportedEntity(final DomainOwnership entity) { - entityManager.persist(entity); } @Override protected Set> getRequiredEntities() { - final Set> classes = new HashSet<>(); classes.add(CcmApplication.class); classes.add(Domain.class); @@ -68,4 +67,26 @@ public class DomainOwnershipImExporter return classes; } + @Override + protected DomainOwnership reloadEntity(final DomainOwnership entity) { + try { + return entityManager + .createNamedQuery( + "DomainOwnership.findById", + DomainOwnership.class + ) + .setParameter( + "ownershipId", + Objects.requireNonNull(entity.getOwnershipId()) + ).getSingleResult(); + } catch (NoResultException ex) { + throw new IllegalArgumentException( + String.format( + "DomainOwnership entity %s not found in database.", + Objects.toString(entity) + ) + ); + } + } + } diff --git a/ccm-core/src/main/java/org/libreccm/core/ResourceTypeImExporter.java b/ccm-core/src/main/java/org/libreccm/core/ResourceTypeImExporter.java index 8262c43ed..771e3d05a 100644 --- a/ccm-core/src/main/java/org/libreccm/core/ResourceTypeImExporter.java +++ b/ccm-core/src/main/java/org/libreccm/core/ResourceTypeImExporter.java @@ -23,6 +23,7 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.Collections; +import java.util.Objects; import java.util.Set; import javax.inject.Inject; @@ -45,15 +46,30 @@ public class ResourceTypeImExporter @Override protected void saveImportedEntity(final ResourceType entity) { - repository.save(entity); } @Override protected Set> getRequiredEntities() { - return Collections.emptySet(); } + + @Override + protected ResourceType reloadEntity(final ResourceType entity) { + return repository + .findById( + Objects.requireNonNull(entity).getResourceTypeId() + ) + .orElseThrow( + () -> new IllegalArgumentException( + String.format( + "The provided ResourceType %s was not found in the " + + "database.", + Objects.toString(entity) + ) + ) + ); + } diff --git a/ccm-core/src/main/java/org/libreccm/imexport/AbstractEntityImExporter.java b/ccm-core/src/main/java/org/libreccm/imexport/AbstractEntityImExporter.java index e4bbeb207..02ea6ca81 100644 --- a/ccm-core/src/main/java/org/libreccm/imexport/AbstractEntityImExporter.java +++ b/ccm-core/src/main/java/org/libreccm/imexport/AbstractEntityImExporter.java @@ -62,7 +62,6 @@ public abstract class AbstractEntityImExporter { * containers usually create a {@link java.lang.reflect.Proxy} class and * there is no portable way to unproxy a class. * - * * @return A {@link Set} of exportable entity classes which should be * processed before the entities which are processed by this * implementation. If the implementation has no dependencies an @@ -72,7 +71,6 @@ public abstract class AbstractEntityImExporter { @Transactional(Transactional.TxType.REQUIRED) public T importEntity(final String data) throws ImportExpection { - try { final T entity = objectMapper.readValue(data, getEntityClass()); saveImportedEntity(entity); @@ -80,13 +78,26 @@ public abstract class AbstractEntityImExporter { } catch (IOException ex) { throw new ImportExpection(ex); } - } + protected abstract void saveImportedEntity(T entity); + + /** + * Export an entity (as JSON). There should be no need to overwrite this + * method. + * + * @param entity The entity to export. + * + * @return The entity as JSON + * + * @throws ExportException If an error occurs. + */ @Transactional(Transactional.TxType.REQUIRED) public String exportEntity(final Exportable entity) throws ExportException { + @SuppressWarnings("unchecked") + final T export = reloadEntity((T) entity); try { - return objectMapper.writeValueAsString(entity); + return objectMapper.writeValueAsString(export); } catch (JsonProcessingException ex) { throw new ExportException(String.format( "Failed to export entity \"%s\" of type \"%s\".", @@ -94,9 +105,17 @@ public abstract class AbstractEntityImExporter { getEntityClass().getName()), ex); } - } - protected abstract void saveImportedEntity(T entity); + /** + * Reloads the entity to export. Entities become detacted for several + * reasons before they are passed to the null {@link #exportEntity(org.libreccm.imexport.Exportable) method. The + * implementation of this should reload the passed entity. + * + * @param entity The entity to reload. + * + * @return The reloaded entity + */ + protected abstract T reloadEntity(final T entity); } diff --git a/ccm-core/src/main/java/org/libreccm/security/GroupImExporter.java b/ccm-core/src/main/java/org/libreccm/security/GroupImExporter.java index 669c6bd45..f3185f2ee 100644 --- a/ccm-core/src/main/java/org/libreccm/security/GroupImExporter.java +++ b/ccm-core/src/main/java/org/libreccm/security/GroupImExporter.java @@ -23,6 +23,7 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.Collections; +import java.util.Objects; import java.util.Set; import javax.enterprise.context.RequestScoped; @@ -32,37 +33,48 @@ import javax.transaction.Transactional; /** * Exporter/Importer for {@link Group}s. - * + * * @author Jens Pelzetter */ @RequestScoped @Processes(Group.class) public class GroupImExporter extends AbstractEntityImExporter { - + @Inject private EntityManager entityManager; - + @Inject private GroupRepository groupRepository; - + @Override public Class getEntityClass() { return Group.class; } - + @Override @Transactional(Transactional.TxType.REQUIRED) protected void saveImportedEntity(final Group entity) { - entity.setPartyId(0); -// groupRepository.save(entity); entityManager.persist(entity); } - + @Override protected Set> getRequiredEntities() { - return Collections.emptySet(); } - + + @Override + protected Group reloadEntity(final Group entity) { + return groupRepository + .findById(Objects.requireNonNull(entity).getPartyId()) + .orElseThrow( + () -> new IllegalArgumentException( + String.format( + "Group entity %s was not found in database.", + Objects.toString(entity) + ) + ) + ); + } + } diff --git a/ccm-core/src/main/java/org/libreccm/security/GroupMembership.java b/ccm-core/src/main/java/org/libreccm/security/GroupMembership.java index 533f97c36..580713cd6 100644 --- a/ccm-core/src/main/java/org/libreccm/security/GroupMembership.java +++ b/ccm-core/src/main/java/org/libreccm/security/GroupMembership.java @@ -56,14 +56,25 @@ import javax.persistence.Table; @Entity @Table(name = "GROUP_MEMBERSHIPS", schema = DB_SCHEMA) @NamedQueries({ - @NamedQuery(name = "GroupMembership.findByUuid", - query = "SELECT m FROM GroupMembership m WHERE m.uuid = :uuid"), - @NamedQuery(name = "GroupMembership.findByGroupAndUser", - query = "SELECT m FROM GroupMembership m " - + "WHERE m.member = :member AND m.group = :group")}) + @NamedQuery( + name = "GroupMembership.findById", + query + = "SELECT m FROM GroupMembership m WHERE m.membershipId = :membershipId" + ), + @NamedQuery( + name = "GroupMembership.findByUuid", + query = "SELECT m FROM GroupMembership m WHERE m.uuid = :uuid" + ), + @NamedQuery( + name = "GroupMembership.findByGroupAndUser", + query = "SELECT m FROM GroupMembership m " + + "WHERE m.member = :member AND m.group = :group") +}) @XmlRootElement(name = "group-membership", namespace = CORE_XML_NS) -@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, - property = "uuid") +@JsonIdentityInfo( + generator = ObjectIdGenerators.PropertyGenerator.class, + property = "uuid" +) public class GroupMembership implements Serializable, Exportable { private static final long serialVersionUID = 83192968306850665L; diff --git a/ccm-core/src/main/java/org/libreccm/security/GroupMembershipImExporter.java b/ccm-core/src/main/java/org/libreccm/security/GroupMembershipImExporter.java index fdda99ce8..a9a513591 100644 --- a/ccm-core/src/main/java/org/libreccm/security/GroupMembershipImExporter.java +++ b/ccm-core/src/main/java/org/libreccm/security/GroupMembershipImExporter.java @@ -23,10 +23,12 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import javax.inject.Inject; import javax.persistence.EntityManager; +import javax.persistence.NoResultException; import javax.transaction.Transactional; /** @@ -36,9 +38,9 @@ import javax.transaction.Transactional; * @author Jens Pelzetter */ @Processes(GroupMembership.class) -public class GroupMembershipImExporter +public class GroupMembershipImExporter extends AbstractEntityImExporter { - + @Inject private EntityManager entityManager; @@ -49,7 +51,6 @@ public class GroupMembershipImExporter @Override protected Set> getRequiredEntities() { - final Set> entities = new HashSet<>(); entities.add(User.class); entities.add(Group.class); @@ -60,9 +61,30 @@ public class GroupMembershipImExporter @Override @Transactional(Transactional.TxType.REQUIRED) protected void saveImportedEntity(final GroupMembership entity) { - entity.setMembershipId(0); entityManager.persist(entity); } + @Override + protected GroupMembership reloadEntity(final GroupMembership entity) { + try { + return entityManager + .createNamedQuery( + "GroupMembership.findById", GroupMembership.class + ) + .setParameter( + "membershipId", + Objects.requireNonNull(entity).getMembershipId() + ) + .getSingleResult(); + } catch (NoResultException ex) { + throw new IllegalArgumentException( + String.format( + "GroupMembership entity %s does not exist in the database.", + Objects.toString(entity) + ) + ); + } + } + } diff --git a/ccm-core/src/main/java/org/libreccm/security/PermissionImExporter.java b/ccm-core/src/main/java/org/libreccm/security/PermissionImExporter.java index f51633d09..fdf832a8f 100644 --- a/ccm-core/src/main/java/org/libreccm/security/PermissionImExporter.java +++ b/ccm-core/src/main/java/org/libreccm/security/PermissionImExporter.java @@ -23,6 +23,7 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import javax.enterprise.context.RequestScoped; @@ -30,16 +31,16 @@ import javax.inject.Inject; /** * Exporter/Importer for {@link Permission}s. - * + * * @author Jens Pelzetter */ @RequestScoped @Processes(Permission.class) -public class PermissionImExporter extends AbstractEntityImExporter{ +public class PermissionImExporter extends AbstractEntityImExporter { @Inject private PermissionRepository permissionRepository; - + @Override public Class getEntityClass() { return Permission.class; @@ -47,19 +48,29 @@ public class PermissionImExporter extends AbstractEntityImExporter{ @Override protected void saveImportedEntity(final Permission entity) { - permissionRepository.save(entity); } @Override protected Set> getRequiredEntities() { - final Set> classes = new HashSet<>(); classes.add(Role.class); - + return classes; } - - - + + @Override + protected Permission reloadEntity(final Permission entity) { + return permissionRepository + .findById(Objects.requireNonNull(entity).getPermissionId()) + .orElseThrow( + () -> new IllegalArgumentException( + String.format( + "Permission entity %s not found in the database.", + Objects.toString(entity) + ) + ) + ); + } + } diff --git a/ccm-core/src/main/java/org/libreccm/security/RoleImExporter.java b/ccm-core/src/main/java/org/libreccm/security/RoleImExporter.java index 2f2656163..ace2a41ac 100644 --- a/ccm-core/src/main/java/org/libreccm/security/RoleImExporter.java +++ b/ccm-core/src/main/java/org/libreccm/security/RoleImExporter.java @@ -18,44 +18,85 @@ */ package org.libreccm.security; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.libreccm.core.UnexpectedErrorException; import org.libreccm.imexport.AbstractEntityImExporter; import org.libreccm.imexport.ExportException; import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.Collections; +import java.util.Objects; import java.util.Set; +import javax.enterprise.context.Dependent; import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.transaction.Transactional; /** * Exporter/Importer for {@link Role}s. - * + * * @author Jens Pelzetter */ +@Dependent @Processes(Role.class) public class RoleImExporter extends AbstractEntityImExporter { + private static final Logger LOGGER = LogManager.getLogger( + RoleImExporter.class); + + @Inject + private EntityManager entityManager; + @Inject private RoleRepository roleRepository; - + @Override public Class getEntityClass() { return Role.class; } + @Transactional(Transactional.TxType.REQUIRED) + @Override + public String exportEntity(final Exportable entity) throws ExportException { + final Role role = roleRepository + .findById(((Role) entity).getRoleId()) + .orElseThrow( + () -> new IllegalArgumentException( + String.format( + "Provided entity %d does not exist in database.", + entity) + ) + ); + role.getDescription().getValues().forEach((locale, value) -> LOGGER + .info("{}: {}", locale, value)); + return super.exportEntity(entity); + } + @Override protected void saveImportedEntity(final Role entity) { - roleRepository.save(entity); } @Override protected Set> getRequiredEntities() { - return Collections.emptySet(); } - - - + + @Override + protected Role reloadEntity(final Role entity) { + return roleRepository + .findById(Objects.requireNonNull(entity).getRoleId()) + .orElseThrow( + () -> new IllegalArgumentException( + String.format( + "Role entity %s not found in database.", + Objects.toString(entity) + ) + ) + ); + } + } diff --git a/ccm-core/src/main/java/org/libreccm/security/RoleMembership.java b/ccm-core/src/main/java/org/libreccm/security/RoleMembership.java index 6aebd8d9c..af8c01d28 100644 --- a/ccm-core/src/main/java/org/libreccm/security/RoleMembership.java +++ b/ccm-core/src/main/java/org/libreccm/security/RoleMembership.java @@ -56,11 +56,20 @@ import javax.persistence.Table; @Entity @Table(name = "ROLE_MEMBERSHIPS", schema = DB_SCHEMA) @NamedQueries({ - @NamedQuery(name = "RoleMembership.findByUuid", - query = "SELECT m FROM RoleMembership m WHERE m.uuid = :uuid"), - @NamedQuery(name = "RoleMembership.findByRoleAndMember", - query = "SELECT m FROM RoleMembership m " - + "WHERE m.member = :member AND m.role = :role") + @NamedQuery( + name = "RoleMembership.findById", + query + = "SELECT m FROM RoleMembership m WHERE m.membershipId = :membershipId" + ), + @NamedQuery( + name = "RoleMembership.findByUuid", + query = "SELECT m FROM RoleMembership m WHERE m.uuid = :uuid" + ), + @NamedQuery( + name = "RoleMembership.findByRoleAndMember", + query + = "SELECT m FROM RoleMembership m WHERE m.member = :member AND m.role = :role" + ) }) @XmlRootElement(name = "role-membership", namespace = CORE_XML_NS) @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, diff --git a/ccm-core/src/main/java/org/libreccm/security/RoleMembershipImExporter.java b/ccm-core/src/main/java/org/libreccm/security/RoleMembershipImExporter.java index 0c9d82f10..c3679a1d8 100644 --- a/ccm-core/src/main/java/org/libreccm/security/RoleMembershipImExporter.java +++ b/ccm-core/src/main/java/org/libreccm/security/RoleMembershipImExporter.java @@ -23,15 +23,17 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import javax.inject.Inject; import javax.persistence.EntityManager; +import javax.persistence.NoResultException; import javax.transaction.Transactional; /** * Exporter/Importer for {@link RoleMembership}s. - * + * * @author Jens Pelzetter */ @Processes(RoleMembership.class) @@ -49,19 +51,38 @@ public class RoleMembershipImExporter @Override @Transactional(Transactional.TxType.REQUIRED) protected void saveImportedEntity(final RoleMembership entity) { - entityManager.persist(entity); } @Override protected Set> getRequiredEntities() { - final Set> classes = new HashSet<>(); classes.add(User.class); classes.add(Group.class); classes.add(Role.class); - + return classes; } + @Override + protected RoleMembership reloadEntity(final RoleMembership entity) { + try { + return entityManager + .createNamedQuery( + "RoleMembership.findById", RoleMembership.class + ) + .setParameter( + "membershipId", + Objects.requireNonNull(entity).getMembershipId() + ).getSingleResult(); + } catch (NoResultException ex) { + throw new IllegalArgumentException( + String.format( + "RoleMembeship entity %s not found in database.", + Objects.toString(entity) + ) + ); + } + } + } diff --git a/ccm-core/src/main/java/org/libreccm/security/UserImExporter.java b/ccm-core/src/main/java/org/libreccm/security/UserImExporter.java index 5f4e29943..9abe63a00 100644 --- a/ccm-core/src/main/java/org/libreccm/security/UserImExporter.java +++ b/ccm-core/src/main/java/org/libreccm/security/UserImExporter.java @@ -23,6 +23,7 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.Collections; +import java.util.Objects; import java.util.Set; import javax.enterprise.context.RequestScoped; @@ -32,7 +33,7 @@ import javax.transaction.Transactional; /** * Exporter/Importer for users. - * + * * @author Jens Pelzetter */ @RequestScoped @@ -41,7 +42,7 @@ public class UserImExporter extends AbstractEntityImExporter { @Inject private EntityManager entityManager; - + @Inject private UserRepository userRepository; @@ -53,7 +54,6 @@ public class UserImExporter extends AbstractEntityImExporter { @Override @Transactional(Transactional.TxType.REQUIRED) protected void saveImportedEntity(final User entity) { - // Reset partyId. entity.setPartyId(0); // userRepository.save(entity); @@ -62,8 +62,21 @@ public class UserImExporter extends AbstractEntityImExporter { @Override protected Set> getRequiredEntities() { - return Collections.emptySet(); } + @Override + protected User reloadEntity(final User entity) { + return userRepository + .findById(Objects.requireNonNull(entity).getPartyId()) + .orElseThrow( + () -> new IllegalArgumentException( + String.format( + "User entity %s was not found in database.", + Objects.toString(entity) + ) + ) + ); + } + } diff --git a/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ExportTask.java b/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ExportTask.java new file mode 100644 index 000000000..6ecbc907e --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ExportTask.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 LibreCCM Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.ui.admin.imexport; + +import org.libreccm.imexport.Exportable; + +import java.time.LocalDate; +import java.util.Collection; +import java.util.Collections; + +/** + * + * @author Jens Pelzetter + */ +public class ExportTask { + + private final String name; + + private final LocalDate started; + + private final Collection entities; + + private final ExportTaskStatus status; + + public ExportTask( + final String name, + final LocalDate started, + final Collection entities, + final ExportTaskStatus status + ) { + this.name = name; + this.started = started; + this.entities = entities; + this.status = status; + } + + public String getName() { + return name; + } + + public LocalDate getStarted() { + return started; + } + + public Collection getEntities() { + return Collections.unmodifiableCollection(entities); + } + + public ExportTaskStatus getStatus() { + return status; + } + + + +} diff --git a/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ExportTaskStatus.java b/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ExportTaskStatus.java new file mode 100644 index 000000000..9b49f1bc8 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ExportTaskStatus.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2020 LibreCCM Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.ui.admin.imexport; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Comparator; +import java.util.Objects; + +/** + * + * @author Jens Pelzetter + */ +public class ExportTaskStatus implements Comparable { + + private String name; + + private LocalDateTime started; + + private ImExportTaskStatusEnum status; + + private Throwable exception; + + public String getName() { + return name; + } + + protected void setName(final String name) { + this.name = name; + } + + public LocalDateTime getStarted() { + return started; + } + + protected void setStarted(final LocalDateTime started) { + this.started = started; + } + + public ImExportTaskStatusEnum getStatus() { + return status; + } + + protected void setStatus(final ImExportTaskStatusEnum status) { + this.status = status; + } + + public Throwable getException() { + return exception; + } + + protected void setException(final Throwable exception) { + this.exception = exception; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 97 * hash + Objects.hashCode(name); + hash = 97 * hash + Objects.hashCode(started); + hash = 97 * hash + Objects.hashCode(status); + return hash; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof ExportTaskStatus)) { + return false; + } + final ExportTaskStatus other = (ExportTaskStatus) obj; + if (!other.canEqual(this)) { + return false; + } + if (!Objects.equals(name, other.getName())) { + return false; + } + if (!Objects.equals(started, other.getStarted())) { + return false; + } + return status == other.getStatus(); + } + + public boolean canEqual(final Object obj) { + return obj instanceof ImExportTaskStatus; + } + + @Override + public int compareTo(final ExportTaskStatus other) { + return Comparator + .nullsFirst(Comparator + .comparing(ExportTaskStatus::getName) + .thenComparing(ExportTaskStatus::getStarted) + .thenComparing(ExportTaskStatus::getStatus) + ) + .compare(this, other); + } + + @Override + public String toString() { + return String.format( + "%s{ " + + "name = %s, " + + "started = %s, " + + "status = %s, " + + "expection = %s" + + " }", + super.toString(), + name, + DateTimeFormatter.ISO_DATE_TIME.withZone( + ZoneId.systemDefault() + ).format(started), + Objects.toString(status), + Objects.toString(exception) + ); + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImExportTask.java b/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImExportTaskStatus.java similarity index 88% rename from ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImExportTask.java rename to ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImExportTaskStatus.java index da1688804..1601aebe7 100644 --- a/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImExportTask.java +++ b/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImExportTaskStatus.java @@ -18,7 +18,6 @@ */ package org.libreccm.ui.admin.imexport; -import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; @@ -30,7 +29,7 @@ import java.util.concurrent.Future; * * @author Jens Pelzetter */ -public class ImExportTask implements Comparable { +public class ImExportTaskStatus implements Comparable { private String name; @@ -92,10 +91,10 @@ public class ImExportTask implements Comparable { if (obj == null) { return false; } - if (!(obj instanceof ImExportTask)) { + if (!(obj instanceof ImExportTaskStatus)) { return false; } - final ImExportTask other = (ImExportTask) obj; + final ImExportTaskStatus other = (ImExportTaskStatus) obj; if (!other.canEqual(this)) { return false; } @@ -106,15 +105,15 @@ public class ImExportTask implements Comparable { } public boolean canEqual(final Object obj) { - return obj instanceof ImExportTask; + return obj instanceof ImExportTaskStatus; } @Override - public int compareTo(final ImExportTask other) { + public int compareTo(final ImExportTaskStatus other) { return Comparator .nullsFirst(Comparator - .comparing(ImExportTask::getName) - .thenComparing(ImExportTask::getStarted) + .comparing(ImExportTaskStatus::getName) + .thenComparing(ImExportTaskStatus::getStarted) ) .compare(this, other); } diff --git a/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImExportTaskStatusEnum.java b/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImExportTaskStatusEnum.java new file mode 100644 index 000000000..2d4889445 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImExportTaskStatusEnum.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 LibreCCM Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.ui.admin.imexport; + +/** + * + * @author Jens Pelzetter + */ +public enum ImExportTaskStatusEnum { + + ERROR, + FINISHED, + RUNNING, + +} diff --git a/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImExportTasks.java b/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImExportTasks.java index 4e425ac38..51dc46149 100644 --- a/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImExportTasks.java +++ b/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImExportTasks.java @@ -26,9 +26,12 @@ import java.util.concurrent.Future; import javax.ejb.AsyncResult; import javax.ejb.Asynchronous; +import javax.ejb.Singleton; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.ObservesAsync; import javax.inject.Inject; import javax.transaction.Transactional; @@ -36,29 +39,45 @@ import javax.transaction.Transactional; * * @author Jens Pelzetter */ -@Stateless +@ApplicationScoped public class ImExportTasks { @Inject private ImportExport importExport; - @Asynchronous - @Transactional(Transactional.TxType.REQUIRES_NEW) - @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) - public Future startExport( - final Collection entities, - final String exportName - ) { +// @Asynchronous +// @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) +// public Future startExport( +// final Collection entities, +// final String exportName +// ) { +// importExport.exportEntities(entities, exportName); +// return new AsyncResult<>(null); +// } +// +// @Asynchronous +// @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) +// public Future startImport(final String importName) { +// importExport.importEntities(importName); +// return new AsyncResult<>(null); +// } + + @Transactional(Transactional.TxType.REQUIRED) + public ExportTask exportEntities(@ObservesAsync final ExportTask task) { + final Collection entities = task.getEntities(); + final String exportName = task.getName(); + importExport.exportEntities(entities, exportName); - return new AsyncResult<>(null); + task.getStatus().setStatus(ImExportTaskStatusEnum.FINISHED); + return task; } - - @Asynchronous - @Transactional(Transactional.TxType.REQUIRES_NEW) - @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) - public Future startImport(final String importName) { + + @Transactional(Transactional.TxType.REQUIRED) + public void importEntitites(@ObservesAsync final ImExportTaskStatus task) { + final String importName = task.getName(); + importExport.importEntities(importName); - return new AsyncResult<>(null); } + } diff --git a/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImportExportTaskManager.java b/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImportExportTaskManager.java index 546ed1c71..2c5313815 100644 --- a/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImportExportTaskManager.java +++ b/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImportExportTaskManager.java @@ -18,23 +18,22 @@ */ package org.libreccm.ui.admin.imexport; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.libreccm.imexport.Exportable; -import org.libreccm.imexport.ImportExport; +import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; -import java.util.concurrent.Future; -import javax.ejb.AsyncResult; -import javax.ejb.Asynchronous; import javax.ejb.Schedule; import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.Event; import javax.inject.Inject; import javax.inject.Named; import javax.persistence.EntityManager; @@ -51,34 +50,47 @@ import javax.transaction.Transactional; @Named("ImportExportTaskManager") public class ImportExportTaskManager { + private static final Logger LOGGER = LogManager.getLogger( + ImportExportTaskManager.class + ); + @Inject private EntityManager entityManager; @Inject - private ImExportTasks imExportTasks; + private Event exportTaskSender; - private SortedSet exportTasks; + @Inject + private Event importTaskSender; - private SortedSet importTasks; +// @Inject +// private ImExportTasks imExportTasks; + +// private SortedSet exportTasks; +// +// private SortedSet importTasks; + private final SortedSet exportTasks; + + private final SortedSet importTasks; public ImportExportTaskManager() { exportTasks = new TreeSet<>( Comparator.comparing( - ImExportTask::getStarted) - .thenComparing(ImExportTask::getName) + ExportTaskStatus::getStarted) + .thenComparing(ExportTaskStatus::getName) ); importTasks = new TreeSet<>( Comparator.comparing( - ImExportTask::getStarted) - .thenComparing(ImExportTask::getName) + ImportTaskStatus::getStarted) + .thenComparing(ImportTaskStatus::getName) ); } - public SortedSet getExportTasks() { + public SortedSet getExportTasks() { return Collections.unmodifiableSortedSet(exportTasks); } - public SortedSet getImportTasks() { + public SortedSet getImportTasks() { return Collections.unmodifiableSortedSet(importTasks); } @@ -96,20 +108,24 @@ public class ImportExportTaskManager { entities.addAll(entitiesOfType); } - final ImExportTask task = new ImExportTask(); - task.setName(exportName); - task.setStarted(LocalDateTime.now()); - final Future status = imExportTasks.startExport( - entities, exportName - ); - task.setStatus(status); - exportTasks.add(task); + final ExportTaskStatus taskStatus = new ExportTaskStatus(); + taskStatus.setName(exportName); + taskStatus.setStarted(LocalDateTime.now()); +// final Future status = imExportTasks.startExport( +// entities, exportName +// ); + exportTaskSender.fireAsync( + new ExportTask(exportName, LocalDate.now(), entities, taskStatus) + ).handle((task , ex) -> handleExportTaskResult(task, ex, taskStatus)); + + taskStatus.setStatus(ImExportTaskStatusEnum.RUNNING); + exportTasks.add(taskStatus); } // public void exportEntities( // final Collection entities, final String exportName // ) { -// final ImExportTask task = new ImExportTask(); +// final ImExportTaskStatus task = new ImExportTaskStatus(); // task.setName(exportName); // task.setStarted(LocalDate.now()); // final Future status = startExport(entities, exportName); @@ -117,21 +133,22 @@ public class ImportExportTaskManager { // exportTasks.add(task); // } public void importEntities(final String importName) { - final ImExportTask task = new ImExportTask(); - task.setName(importName); - task.setStarted(LocalDateTime.now()); - final Future status = imExportTasks.startImport(importName); - task.setStatus(status); - importTasks.add(task); +// final ImExportTaskStatus task = new ImExportTaskStatus(); +// task.setName(importName); +// task.setStarted(LocalDateTime.now()); +// final Future status = imExportTasks.startImport(importName); +// task.setStatus(status); +// importTasks.add(task); + throw new UnsupportedOperationException(); } @Schedule(hour = "*", minute = "*/5", persistent = false) protected void removeFinishedTasks() { - exportTasks.removeIf(ImExportTask::isDone); - importTasks.removeIf(ImExportTask::isDone); + exportTasks.removeIf(taskStatus -> taskStatus.getStatus() == ImExportTaskStatusEnum.FINISHED); +// importTasks.removeIf(taskStatus -> taskStatus.getStatus() == ImExportTaskStatusEnum.FINISHED); } - public void cancelTask(final ImExportTask task) { + public void cancelTask(final ImExportTaskStatus task) { task.cancel(); } @@ -149,4 +166,18 @@ public class ImportExportTaskManager { ); } + private Object handleExportTaskResult( + final ExportTask task, final Throwable ex, final ExportTaskStatus status + ) { + if (ex == null) { + status.setStatus(ImExportTaskStatusEnum.FINISHED); + } else { + status.setStatus(ImExportTaskStatusEnum.ERROR); + status.setException(ex); + LOGGER.error("Export Task {} failed ", task); + LOGGER.error("with exception:", ex); + } + return task; + } + } diff --git a/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImportTask.java b/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImportTask.java new file mode 100644 index 000000000..013c7406c --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImportTask.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 LibreCCM Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.ui.admin.imexport; + +import java.time.LocalDate; + +/** + * + * @author Jens Pelzetter + */ +public class ImportTask { + + private final String name; + + private final LocalDate started; + + public ImportTask(final String name, final LocalDate started) { + this.name = name; + this.started = started; + } + + public String getName() { + return name; + } + + public LocalDate getStarted() { + return started; + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImportTaskStatus.java b/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImportTaskStatus.java new file mode 100644 index 000000000..c67ecafb9 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/ui/admin/imexport/ImportTaskStatus.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2020 LibreCCM Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.ui.admin.imexport; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Comparator; +import java.util.Objects; +import java.util.concurrent.CompletionStage; + +/** + * + * @author Jens Pelzetter + */ +public class ImportTaskStatus implements Comparable { + + private String name; + + private LocalDateTime started; + + private CompletionStage status; + + public String getName() { + return name; + } + + protected void setName(final String name) { + this.name = name; + } + + public LocalDateTime getStarted() { + return started; + } + + protected void setStarted(final LocalDateTime started) { + this.started = started; + } + + public CompletionStage getStatus() { + return status; + } + + protected void setStatus(final CompletionStage status) { + this.status = status; + } + + public boolean canEqual(final Object obj) { + return obj instanceof ImExportTaskStatus; + } + + @Override + public int compareTo(final ImportTaskStatus other) { + return Comparator + .nullsFirst(Comparator + .comparing(ImportTaskStatus::getName) + .thenComparing(ImportTaskStatus::getStarted) + ) + .compare(this, other); + } + + @Override + public String toString() { + return String.format( + "%s{ " + + "name = %s, " + + "started = %s, " + + "status = %s" + + " }", + super.toString(), + name, + DateTimeFormatter.ISO_DATE_TIME.withZone( + ZoneId.systemDefault() + ).format(started), + Objects.toString(status) + ); + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/web/ApplicationImExporter.java b/ccm-core/src/main/java/org/libreccm/web/ApplicationImExporter.java index 35038e547..cb2a296cd 100644 --- a/ccm-core/src/main/java/org/libreccm/web/ApplicationImExporter.java +++ b/ccm-core/src/main/java/org/libreccm/web/ApplicationImExporter.java @@ -23,6 +23,7 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.Collections; +import java.util.Objects; import java.util.Set; import javax.enterprise.context.RequestScoped; @@ -50,14 +51,26 @@ public class ApplicationImExporter @Override @Transactional(Transactional.TxType.REQUIRED) protected void saveImportedEntity(final CcmApplication entity) { - applicationRepository.save(entity); } @Override protected Set> getRequiredEntities() { - return Collections.emptySet(); } + @Override + protected CcmApplication reloadEntity(final CcmApplication entity) { + return applicationRepository + .findById(Objects.requireNonNull(entity).getObjectId()) + .orElseThrow( + () -> new IllegalArgumentException( + String.format( + "CcmApplication entity %s not found in database.", + Objects.toString(entity) + ) + ) + ); + } + } diff --git a/ccm-core/src/main/java/org/libreccm/workflow/AssignableTaskImExporter.java b/ccm-core/src/main/java/org/libreccm/workflow/AssignableTaskImExporter.java index 13f346ae1..a75311637 100644 --- a/ccm-core/src/main/java/org/libreccm/workflow/AssignableTaskImExporter.java +++ b/ccm-core/src/main/java/org/libreccm/workflow/AssignableTaskImExporter.java @@ -23,6 +23,7 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import javax.enterprise.context.RequestScoped; @@ -31,7 +32,7 @@ import javax.transaction.Transactional; /** * Exporter/Importer for {@link AssignableTask}s. - * + * * @author Tobias Osmers * @author Jens Pelzetter * @@ -51,7 +52,6 @@ public class AssignableTaskImExporter @Override protected Set> getRequiredEntities() { - final Set> entities = new HashSet<>(); entities.add(Workflow.class); @@ -61,8 +61,21 @@ public class AssignableTaskImExporter @Override @Transactional(Transactional.TxType.REQUIRED) protected void saveImportedEntity(final AssignableTask entity) { - assignableTaskRepository.save(entity); } + @Override + protected AssignableTask reloadEntity(final AssignableTask entity) { + return assignableTaskRepository + .findById(Objects.requireNonNull(entity).getTaskId()) + .orElseThrow( + () -> new IllegalArgumentException( + String.format( + "AssignableTask entity %s not found in database.", + Objects.toString(entity) + ) + ) + ); + } + } diff --git a/ccm-core/src/main/java/org/libreccm/workflow/TaskAssignment.java b/ccm-core/src/main/java/org/libreccm/workflow/TaskAssignment.java index 2e4092f45..16cb3f651 100644 --- a/ccm-core/src/main/java/org/libreccm/workflow/TaskAssignment.java +++ b/ccm-core/src/main/java/org/libreccm/workflow/TaskAssignment.java @@ -37,17 +37,39 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; import javax.persistence.Table; /** * Represents the assignment of a {@link AssignableTask} to a {@link Role}. - * + * * @author Jens Pelzetter */ @Entity @Table(name = "WORKFLOW_TASK_ASSIGNMENTS", schema = DB_SCHEMA) -@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, - property = "customAssignId") +@NamedQueries({ + @NamedQuery( + name = "TaskAssignment.findById", + query = "SELECT t FROM TaskAssignment t WHERE t.taskAssignmentId = :assignmentId" + ), + @NamedQuery( + name = "TaskAssignment.findByUuid", + query = "SELECT t FROM TaskAssignment t WHERE t.uuid = :uuid" + ), + @NamedQuery( + name = "TaskAssignment.findByTask", + query = "SELECT t FROM TaskAssignment t WHERE t.task = :task" + ), + @NamedQuery( + name = "TaskAssignment.findByRole", + query = "SELECT t FROM TaskAssignment t WHERE t.role = :role" + ) +}) +@JsonIdentityInfo( + generator = ObjectIdGenerators.PropertyGenerator.class, + property = "customAssignId" +) public class TaskAssignment implements Serializable, Exportable { private static final long serialVersionUID = -4427537363301565707L; @@ -62,7 +84,7 @@ public class TaskAssignment implements Serializable, Exportable { @Column(name = "UUID", unique = true, nullable = false) private String uuid; - + /** * The task. */ @@ -86,7 +108,7 @@ public class TaskAssignment implements Serializable, Exportable { protected void setTaskAssignmentId(final long taskAssignmentId) { this.taskAssignmentId = taskAssignmentId; } - + @Override public String getUuid() { return uuid; @@ -95,7 +117,6 @@ public class TaskAssignment implements Serializable, Exportable { public void setUuid(final String uuid) { this.uuid = uuid; } - public AssignableTask getTask() { return task; diff --git a/ccm-core/src/main/java/org/libreccm/workflow/TaskAssignmentImExporter.java b/ccm-core/src/main/java/org/libreccm/workflow/TaskAssignmentImExporter.java index f4bb4c704..7b685ed79 100644 --- a/ccm-core/src/main/java/org/libreccm/workflow/TaskAssignmentImExporter.java +++ b/ccm-core/src/main/java/org/libreccm/workflow/TaskAssignmentImExporter.java @@ -23,22 +23,24 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.persistence.EntityManager; +import javax.persistence.NoResultException; import javax.transaction.Transactional; /** * Exporter/Importer for {@link TaskAssignment}s. - * + * * @author Tobias Osmers * @author Jens Pelzetter */ @RequestScoped @Processes(TaskAssignment.class) -public class TaskAssignmentImExporter +public class TaskAssignmentImExporter extends AbstractEntityImExporter { @Inject @@ -46,24 +48,44 @@ public class TaskAssignmentImExporter @Override public Class getEntityClass() { - return TaskAssignment.class; } @Override @Transactional(Transactional.TxType.REQUIRED) protected void saveImportedEntity(final TaskAssignment entity) { - entityManager.persist(entity); - + } @Override protected Set> getRequiredEntities() { - + final Set> classes = new HashSet<>(); classes.add(AssignableTask.class); - + return classes; } + + @Override + protected TaskAssignment reloadEntity(final TaskAssignment entity) { + try { + return entityManager + .createNamedQuery( + "TaskAssignment.findById", TaskAssignment.class + ) + .setParameter( + "assignmentId", + Objects.requireNonNull(entity).getTaskAssignmentId() + ).getSingleResult(); + } catch (NoResultException ex) { + throw new IllegalArgumentException( + String.format( + "TaskAssignment entity %s not found in database.", + Objects.toString(entity) + ) + ); + } + } + } diff --git a/ccm-core/src/main/java/org/libreccm/workflow/TaskCommentImExporter.java b/ccm-core/src/main/java/org/libreccm/workflow/TaskCommentImExporter.java index 6ffe6a7c3..1a7d6d1d2 100644 --- a/ccm-core/src/main/java/org/libreccm/workflow/TaskCommentImExporter.java +++ b/ccm-core/src/main/java/org/libreccm/workflow/TaskCommentImExporter.java @@ -23,6 +23,7 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import javax.enterprise.context.RequestScoped; @@ -55,12 +56,25 @@ public class TaskCommentImExporter extends AbstractEntityImExporter @Override protected Set> getRequiredEntities() { - final Set> classes = new HashSet<>(); classes.add(AssignableTask.class); return classes; } + + @Override + protected TaskComment reloadEntity(final TaskComment entity) { + return taskCommentRepository + .findById(Objects.requireNonNull(entity).getCommentId()) + .orElseThrow( + () -> new IllegalArgumentException( + String.format( + "TaskComment entity %s not found in database.", + Objects.toString(entity) + ) + ) + ); + } } diff --git a/ccm-core/src/main/java/org/libreccm/workflow/TaskDependency.java b/ccm-core/src/main/java/org/libreccm/workflow/TaskDependency.java index 2de740200..f5ac216b2 100644 --- a/ccm-core/src/main/java/org/libreccm/workflow/TaskDependency.java +++ b/ccm-core/src/main/java/org/libreccm/workflow/TaskDependency.java @@ -34,6 +34,8 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; import javax.persistence.Table; /** @@ -43,8 +45,28 @@ import javax.persistence.Table; */ @Entity @Table(name = "WORKFLOW_TASK_DEPENDENCIES", schema = CoreConstants.DB_SCHEMA) -@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, - property = "uuid") +@NamedQueries({ + @NamedQuery( + name = "TaskDependency.findById", + query = "SELECT d FROM TaskDependency d WHERE d.taskDependencyId = :dependencyId" + ), + @NamedQuery( + name = "TaskDependency.findByUuid", + query = "SELECT d FROM TaskDependency d WHERE d.uuid = :uuid" + ), + @NamedQuery( + name = "TaskDependency.findByBlockedTask", + query = "SELECT d FROM TaskDependency d WHERE d.blockedTask = :task" + ), + @NamedQuery( + name = "TaskDependency.findByBlockingTask", + query = "SELECT d FROM TaskDependency d WHERE d.blockingTask = :task" + ) +}) +@JsonIdentityInfo( + generator = ObjectIdGenerators.PropertyGenerator.class, + property = "uuid" +) public class TaskDependency implements Serializable, Exportable { private static final long serialVersionUID = -4383255770131633943L; diff --git a/ccm-core/src/main/java/org/libreccm/workflow/TaskDependencyImExporter.java b/ccm-core/src/main/java/org/libreccm/workflow/TaskDependencyImExporter.java index 15293c412..128dce652 100644 --- a/ccm-core/src/main/java/org/libreccm/workflow/TaskDependencyImExporter.java +++ b/ccm-core/src/main/java/org/libreccm/workflow/TaskDependencyImExporter.java @@ -23,22 +23,24 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.persistence.EntityManager; +import javax.persistence.NoResultException; import javax.transaction.Transactional; /** * Exporter/Importer for {@link TaskDependency} entities. - * + * * @author Jens Pelzetter */ @RequestScoped @@ -50,14 +51,26 @@ public class WorkflowImExporter extends AbstractEntityImExporter { @Override @Transactional(Transactional.TxType.REQUIRED) protected void saveImportedEntity(final Workflow entity) { - workflowRepository.save(entity); } @Override protected Set> getRequiredEntities() { - return Collections.emptySet(); } + @Override + protected Workflow reloadEntity(final Workflow entity) { + return workflowRepository + .findById(Objects.requireNonNull(entity).getWorkflowId()) + .orElseThrow( + () -> new IllegalArgumentException( + String.format( + "Workflow entity %s not found in database.", + Objects.toString(entity) + ) + ) + ); + } + } diff --git a/ccm-core/src/main/resources/WEB-INF/views/org/libreccm/ui/admin/imexport/imexport.xhtml b/ccm-core/src/main/resources/WEB-INF/views/org/libreccm/ui/admin/imexport/imexport.xhtml index 9f524b6e9..f5f43b9af 100644 --- a/ccm-core/src/main/resources/WEB-INF/views/org/libreccm/ui/admin/imexport/imexport.xhtml +++ b/ccm-core/src/main/resources/WEB-INF/views/org/libreccm/ui/admin/imexport/imexport.xhtml @@ -88,20 +88,12 @@ - + #{task.name} #{task.started} - - - - #{AdminMessages['imexport.activeexports.table.columns.status.finished']} - - - #{AdminMessages['imexport.activeexports.table.columns.status.running']} - - - + #{task.status} #{AdminMessages['imexport.activeexports.table.columns.actions.button_label']} @@ -135,7 +127,8 @@ - + #{task.name} #{task.started} diff --git a/ccm-docrepo/src/main/java/org/libreccm/docrepo/BlobObjectImExporter.java b/ccm-docrepo/src/main/java/org/libreccm/docrepo/BlobObjectImExporter.java index 2c83abcf2..8c2fc4082 100644 --- a/ccm-docrepo/src/main/java/org/libreccm/docrepo/BlobObjectImExporter.java +++ b/ccm-docrepo/src/main/java/org/libreccm/docrepo/BlobObjectImExporter.java @@ -23,9 +23,10 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.Collections; +import java.util.Objects; import java.util.Set; -import javax.faces.bean.RequestScoped; +import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.transaction.Transactional; @@ -45,7 +46,7 @@ public class BlobObjectImExporter extends AbstractEntityImExporter { private BlobObjectRepository blobObjectRepository; @Override - protected Class getEntityClass() { + public Class getEntityClass() { return BlobObject.class; } @@ -61,4 +62,18 @@ public class BlobObjectImExporter extends AbstractEntityImExporter { return Collections.emptySet(); } + @Override + protected BlobObject reloadEntity(final BlobObject entity) { + return blobObjectRepository + .findById(Objects.requireNonNull(entity).getBlobObjectId()) + .orElseThrow( + () -> new IllegalArgumentException( + String.format( + "BlobObject entity %s not found in database.", + Objects.toString(entity) + ) + ) + ); + } + } diff --git a/ccm-docrepo/src/main/java/org/libreccm/docrepo/FileImExporter.java b/ccm-docrepo/src/main/java/org/libreccm/docrepo/FileImExporter.java index 09f6112a8..afe03f913 100644 --- a/ccm-docrepo/src/main/java/org/libreccm/docrepo/FileImExporter.java +++ b/ccm-docrepo/src/main/java/org/libreccm/docrepo/FileImExporter.java @@ -23,17 +23,17 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.Collections; +import java.util.Objects; import java.util.Set; import javax.enterprise.context.RequestScoped; - import javax.inject.Inject; import javax.transaction.Transactional; /** - * Im/Exporter for importing and exporting {@code File}s from the - * system into a specified file and the other way around. + * Im/Exporter for importing and exporting {@code File}s from the system into a + * specified file and the other way around. * * @author Tobias Osmers * @author Jens Pelzetter @@ -46,7 +46,7 @@ public class FileImExporter extends AbstractEntityImExporter { private FileRepository fileRepository; @Override - protected Class getEntityClass() { + public Class getEntityClass() { return File.class; } @@ -60,6 +60,19 @@ public class FileImExporter extends AbstractEntityImExporter { protected Set> getRequiredEntities() { return Collections.emptySet(); } - - + + @Override + protected File reloadEntity(final File entity) { + return fileRepository + .findById(Objects.requireNonNull(entity).getObjectId()) + .orElseThrow( + () -> new IllegalArgumentException( + String.format( + "File entity %s not found in database", + Objects.toString(entity) + ) + ) + ); + } + } diff --git a/ccm-docrepo/src/main/java/org/libreccm/docrepo/FolderImExporter.java b/ccm-docrepo/src/main/java/org/libreccm/docrepo/FolderImExporter.java index b757b1eff..7645f8307 100644 --- a/ccm-docrepo/src/main/java/org/libreccm/docrepo/FolderImExporter.java +++ b/ccm-docrepo/src/main/java/org/libreccm/docrepo/FolderImExporter.java @@ -22,9 +22,10 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.Collections; +import java.util.Objects; import java.util.Set; -import javax.faces.bean.RequestScoped; +import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.transaction.Transactional; @@ -43,7 +44,7 @@ public class FolderImExporter extends AbstractResourceImExporter { private FolderRepository folderRepository; @Override - protected Class getEntityClass() { + public Class getEntityClass() { return Folder.class; } @@ -58,4 +59,18 @@ public class FolderImExporter extends AbstractResourceImExporter { return Collections.emptySet(); } + @Override + protected Folder reloadEntity(final Folder entity) { + return folderRepository + .findById(Objects.requireNonNull(entity).getObjectId()) + .orElseThrow( + () -> new IllegalArgumentException( + String.format( + "Folder entity %s not found in database.", + Objects.toString(entity) + ) + ) + ); + } + } diff --git a/ccm-docrepo/src/main/java/org/libreccm/docrepo/RepositoryImExporter.java b/ccm-docrepo/src/main/java/org/libreccm/docrepo/RepositoryImExporter.java index bd2e03df2..c159f7ea9 100644 --- a/ccm-docrepo/src/main/java/org/libreccm/docrepo/RepositoryImExporter.java +++ b/ccm-docrepo/src/main/java/org/libreccm/docrepo/RepositoryImExporter.java @@ -23,9 +23,10 @@ import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Processes; import java.util.Collections; +import java.util.Objects; import java.util.Set; -import javax.faces.bean.RequestScoped; +import javax.enterprise.context.RequestScoped; import javax.inject.Inject; /** @@ -40,7 +41,7 @@ public class RepositoryImExporter extends AbstractEntityImExporter { private RepositoryRepository repositoryRepository; @Override - protected Class getEntityClass() { + public Class getEntityClass() { return Repository.class; } @@ -53,7 +54,19 @@ public class RepositoryImExporter extends AbstractEntityImExporter { protected Set> getRequiredEntities() { return Collections.emptySet(); } - - + + @Override + protected Repository reloadEntity(final Repository entity) { + return repositoryRepository + .findById(Objects.requireNonNull(entity).getObjectId()) + .orElseThrow( + () -> new IllegalArgumentException( + String.format( + "Repository entity %s not found in database.", + Objects.toString(entity) + ) + ) + ); + } + } - \ No newline at end of file