From ab03852a6825cb69d62a98c7e8e2d3c9bac3f8d1 Mon Sep 17 00:00:00 2001 From: rkv Date: Fri, 23 Nov 2012 09:25:22 +0000 Subject: [PATCH] Check for duplication is added when creating context types and knowledge types. INFO LOG messages are printed if types already exist. --- .../src/conf/log-messages.properties | 5 +- .../src/conf/log-messages_en.properties | 7 +- .../common/properties/MessageKeyEnum.java | 14 +- .../dal/bo/som/SimulationContextType.java | 237 +++++++++++------- .../service/DocumentTypeServiceImpl.java | 8 + .../KnowledgeElementTypeServiceImpl.java | 58 +++-- .../SimulationContextTypeServiceImpl.java | 30 ++- .../splat/service/TestScenarioService.java | 109 +++++--- 8 files changed, 308 insertions(+), 160 deletions(-) diff --git a/Workspace/Siman-Common/src/conf/log-messages.properties b/Workspace/Siman-Common/src/conf/log-messages.properties index 05739d5..dfee6a4 100644 --- a/Workspace/Siman-Common/src/conf/log-messages.properties +++ b/Workspace/Siman-Common/src/conf/log-messages.properties @@ -5,4 +5,7 @@ LCK-000003=Lock reference protected and can be only deleted or updated by user { LCK-000004=Lock reference is timeout and could have been modified by user {2}. STD-000001=Unable to re-index the study #{1}, reason: {2} SCN-000001=Scenario doesn't contain the step number #{1} -SCN-000002=Scenario doesn't contain the document #{1} \ No newline at end of file +SCN-000002=Scenario doesn't contain the document #{1} +KNT-000001=Knowledge element type "{1}" already exists +SCT-000001=Simulation context type "{1}" already exists +DCT-000001=Document type "{1}" already exists diff --git a/Workspace/Siman-Common/src/conf/log-messages_en.properties b/Workspace/Siman-Common/src/conf/log-messages_en.properties index 708f15d..dfee6a4 100644 --- a/Workspace/Siman-Common/src/conf/log-messages_en.properties +++ b/Workspace/Siman-Common/src/conf/log-messages_en.properties @@ -3,4 +3,9 @@ LCK-000001=Lock reference already exists for user {2}. LCK-000002=Lock reference does not exists. LCK-000003=Lock reference protected and can be only deleted or updated by user {2}. LCK-000004=Lock reference is timeout and could have been modified by user {2}. -STD-000001="Unable to re-index the study #{1}, reason: {2}" \ No newline at end of file +STD-000001=Unable to re-index the study #{1}, reason: {2} +SCN-000001=Scenario doesn't contain the step number #{1} +SCN-000002=Scenario doesn't contain the document #{1} +KNT-000001=Knowledge element type "{1}" already exists +SCT-000001=Simulation context type "{1}" already exists +DCT-000001=Document type "{1}" already exists diff --git a/Workspace/Siman-Common/src/org/splat/common/properties/MessageKeyEnum.java b/Workspace/Siman-Common/src/org/splat/common/properties/MessageKeyEnum.java index 12409ae..0d4b54a 100644 --- a/Workspace/Siman-Common/src/org/splat/common/properties/MessageKeyEnum.java +++ b/Workspace/Siman-Common/src/org/splat/common/properties/MessageKeyEnum.java @@ -44,7 +44,19 @@ public enum MessageKeyEnum { /** * Scenario doesn't contain the document #{1}. */ - SCN_000002("SCN-000002"); + SCN_000002("SCN-000002"), + /** + * Simulation context type "{1}" already exists. + */ + KNT_000001("KNT-000001"), + /** + * Knowledge element type "{1}" already exists. + */ + SCT_000001("SCT-000001"), + /** + * Document type "{1}" already exists. + */ + DCT_000001("DCT-000001"); /** * Value. diff --git a/Workspace/Siman-Common/src/org/splat/dal/bo/som/SimulationContextType.java b/Workspace/Siman-Common/src/org/splat/dal/bo/som/SimulationContextType.java index aa18647..42aaf44 100644 --- a/Workspace/Siman-Common/src/org/splat/dal/bo/som/SimulationContextType.java +++ b/Workspace/Siman-Common/src/org/splat/dal/bo/som/SimulationContextType.java @@ -1,4 +1,5 @@ package org.splat.dal.bo.som; + /** * * @author Daniel Brunier-Coulin @@ -11,121 +12,183 @@ import org.splat.dal.bo.kernel.Persistent; import org.splat.kernel.InvalidPropertyException; import org.splat.service.technical.ProjectSettingsService; - +/** + * Persistent simulation context type. + */ public class SimulationContextType extends Persistent implements Serializable { - -// Persistent fields - private String name; - private ProgressState state; - private int step; -// Required by the serialization + /** + * Serialization version id. + */ private static final long serialVersionUID = 4819425038576161242L; -// ============================================================================================================================== -// Constructors -// ============================================================================================================================== - -// Search properties class - public static class Properties { -// ------------------------------ - private ProgressState state = null; - private ProjectSettingsService.Step step = null; - - public ProgressState getProgressState () { - return state; - } - public ProjectSettingsService.Step getStep () { - return step; - } - public Properties setProgressState (final ProgressState state) { - this.state = state; - return this; - } - public Properties setStep (final ProjectSettingsService.Step step) { - this.step = step; - return this; - } - } -// Database fetch constructor - protected SimulationContextType () { - } -// Initialization constructor - public SimulationContextType (final String name, final ProjectSettingsService.Step step) throws InvalidPropertyException { -// ------------------------------------------------------------------------ - super(); - this.name = name; - this.state = ProgressState.inCHECK; - this.step = step.getNumber(); - } - -// ============================================================================================================================== -// Public member functions -// ============================================================================================================================== - - @Override + // Persistent fields + /** + * Context type name. + */ + private String name; + /** + * Context progress state. + */ + private ProgressState state; + /** + * Context type first applicable step. + */ + private int step; + + // ============================================================================================================================== + // Constructors + // ============================================================================================================================== + + /** + * Search properties class. + */ + public static class Properties { + private ProgressState state = null; + private ProjectSettingsService.Step step = null; + + public ProgressState getProgressState() { + return state; + } + + public ProjectSettingsService.Step getStep() { + return step; + } + + public Properties setProgressState(final ProgressState state) { + this.state = state; + return this; + } + + public Properties setStep(final ProjectSettingsService.Step step) { + this.step = step; + return this; + } + } + + /** + * Database fetch constructor. + */ + protected SimulationContextType() { + super(); + } + + /** + * Initialization constructor. + * + * @param name + * context type name + * @param step + * first applicable step for this type of context + * @throws InvalidPropertyException + * from constructor of parent class + */ + public SimulationContextType(final String name, + final ProjectSettingsService.Step step) + throws InvalidPropertyException { + super(); + this.name = name; + this.state = ProgressState.inCHECK; + this.step = step.getNumber(); + } + + // ============================================================================================================================== + // Public member functions + // ============================================================================================================================== + + /** + * Saved context types are equal if their persistent ids are equal.
+ * Before becoming persistent context types are compared by their names. + * + * @param entity + * object to compare with + * @return true if this context type equals to the given object + * @see org.splat.dal.bo.kernel.Persistent#equals(java.lang.Object) + */ + @Override public boolean equals(final Object entity) { -// ------------------------------------ - if (entity == null) { - return false; + + if (entity == null) { + return false; + } + if (entity instanceof String) { + return this.name.equals(entity); // Names are unique + } else if (entity instanceof SimulationContextType) { + SimulationContextType object = (SimulationContextType) entity; + long he = object.getIndex(); + long me = this.getIndex(); + if (me * he == 0) { + return this.getName().equals(object.getName()); + } else { + return (he == me); + } + } else { + return false; + } } - if (entity instanceof String) { - return this.name.equals(entity); // Names are unique - } else if (entity instanceof SimulationContextType) { - SimulationContextType object = (SimulationContextType)entity; - long he = object.getIndex(); - long me = this.getIndex(); - if (me*he != 0) { - return (he == me); - } else { - return this.getName().equals(object.getName()); - } - } else { - return false; - } - } - - public String getName () { -// ------------------------ - return name; - } - - public boolean isAttachedTo (final ProjectSettingsService.Step step) { -// ------------------------------------------------------- - if (this.step == step.getNumber()) { - return true; + + /** + * Get context type name. + * + * @return context type name + */ + public String getName() { + return name; } - return false; - } - - public boolean isApproved () { -// ---------------------------- - return (state == ProgressState.APPROVED); - } - /** + + /** + * Check if contexts of this type are attached to the given study step. + * + * @param step + * the study step + * @return true if contexts of this type are attached to the given study step + */ + public boolean isAttachedTo(final ProjectSettingsService.Step step) { + return (this.step == step.getNumber()); + } + + /** + * Check if the context is approved. + * + * @return true if the context is approved + */ + public boolean isApproved() { + return (state == ProgressState.APPROVED); + } + + /** * Get the state. + * * @return the state */ public ProgressState getState() { return state; } + /** * Set the state. - * @param state the state to set + * + * @param state + * the state to set */ public void setState(final ProgressState state) { this.state = state; } + /** * Get the step. + * * @return the step */ public int getStep() { return step; } + /** * Set the step. - * @param step the step to set + * + * @param step + * the step to set */ public void setStep(final int step) { this.step = step; diff --git a/Workspace/Siman-Common/src/org/splat/service/DocumentTypeServiceImpl.java b/Workspace/Siman-Common/src/org/splat/service/DocumentTypeServiceImpl.java index 63f511f..33b15cf 100644 --- a/Workspace/Siman-Common/src/org/splat/service/DocumentTypeServiceImpl.java +++ b/Workspace/Siman-Common/src/org/splat/service/DocumentTypeServiceImpl.java @@ -15,12 +15,14 @@ import java.util.List; import org.hibernate.Hibernate; import org.hibernate.criterion.Order; import org.hibernate.criterion.Restrictions; +import org.splat.common.properties.MessageKeyEnum; import org.splat.dal.bo.som.DocumentType; import org.splat.dal.bo.som.ProgressState; import org.splat.dal.dao.som.DocumentTypeDAO; import org.splat.kernel.InvalidPropertyException; import org.splat.kernel.MissedPropertyException; import org.splat.kernel.MultiplyDefinedException; +import org.splat.log.AppLogger; import org.splat.service.technical.ProjectSettingsService; import org.springframework.transaction.annotation.Transactional; @@ -31,6 +33,11 @@ import org.springframework.transaction.annotation.Transactional; */ public class DocumentTypeServiceImpl implements DocumentTypeService { + /** + * The logger for the service. + */ + public final static AppLogger LOG = AppLogger + .getLogger(DocumentTypeServiceImpl.class); /** * Injected document type DAO. */ @@ -110,6 +117,7 @@ public class DocumentTypeServiceImpl implements DocumentTypeService { type = new DocumentType(tprop); getDocumentTypeDAO().create(type); } else { + LOG.info(MessageKeyEnum.DCT_000001.toString(), tprop.getName()); type.setProperties(tprop); // Update properties of persistent type } return type; diff --git a/Workspace/Siman-Common/src/org/splat/service/KnowledgeElementTypeServiceImpl.java b/Workspace/Siman-Common/src/org/splat/service/KnowledgeElementTypeServiceImpl.java index f644d14..db8e4cf 100644 --- a/Workspace/Siman-Common/src/org/splat/service/KnowledgeElementTypeServiceImpl.java +++ b/Workspace/Siman-Common/src/org/splat/service/KnowledgeElementTypeServiceImpl.java @@ -14,9 +14,11 @@ import java.util.List; import org.hibernate.criterion.Order; import org.hibernate.criterion.Restrictions; +import org.splat.common.properties.MessageKeyEnum; import org.splat.dal.bo.som.KnowledgeElementType; import org.splat.dal.bo.som.ProgressState; import org.splat.dal.dao.som.KnowledgeElementTypeDAO; +import org.splat.log.AppLogger; import org.springframework.transaction.annotation.Transactional; /** @@ -27,6 +29,11 @@ import org.springframework.transaction.annotation.Transactional; public class KnowledgeElementTypeServiceImpl implements KnowledgeElementTypeService { + /** + * The logger for the service. + */ + public final static AppLogger LOG = AppLogger + .getLogger(KnowledgeElementTypeServiceImpl.class); /** * Injected knowledge element type DAO. */ @@ -39,10 +46,15 @@ public class KnowledgeElementTypeServiceImpl implements * the new type name * @return the created knowledge type */ - public KnowledgeElementType createType(String name) { - // TODO: Check for duplicate definition - KnowledgeElementType kelt = new KnowledgeElementType(name); - getKnowledgeElementTypeDAO().create(kelt); + public KnowledgeElementType createType(final String name) { + KnowledgeElementType kelt = getKnowledgeElementTypeDAO() + .findByCriteria(Restrictions.eq("name", name)); + if (kelt == null) { + kelt = new KnowledgeElementType(name); + getKnowledgeElementTypeDAO().create(kelt); + } else { + LOG.info(MessageKeyEnum.KNT_000001.toString(), name); + } return kelt; } @@ -62,7 +74,7 @@ public class KnowledgeElementTypeServiceImpl implements * the progress state * @return the list of found knowledge types */ - public List selectTypesWhere(ProgressState state) { + public List selectTypesWhere(final ProgressState state) { return getKnowledgeElementTypeDAO().getFilteredList( Restrictions.eq("state", state), Order.asc("rid")); } @@ -74,7 +86,7 @@ public class KnowledgeElementTypeServiceImpl implements * the knowledge type name * @return the found knowledge type */ - public KnowledgeElementType selectType(String name) { + public KnowledgeElementType selectType(final String name) { return getKnowledgeElementTypeDAO().findByCriteria( Restrictions.eq("name", name)); } @@ -86,7 +98,7 @@ public class KnowledgeElementTypeServiceImpl implements * the id of a knowledge type * @return the found knowledge type */ - public KnowledgeElementType selectType(long index) { + public KnowledgeElementType selectType(final long index) { return getKnowledgeElementTypeDAO().get(index); } @@ -98,14 +110,15 @@ public class KnowledgeElementTypeServiceImpl implements * @return true if approval succeeded */ @Transactional - public boolean approve(KnowledgeElementType kelt) { - if (kelt.getState() != ProgressState.inCHECK) - return false; - kelt.setState(ProgressState.APPROVED); // The type name is supposed being localized - if (kelt.isSaved()) { - getKnowledgeElementTypeDAO().update(kelt); + public boolean approve(final KnowledgeElementType kelt) { + boolean res = (kelt.getState() == ProgressState.inCHECK); + if (res) { + kelt.setState(ProgressState.APPROVED); // The type name is supposed being localized + if (kelt.isSaved()) { + getKnowledgeElementTypeDAO().update(kelt); + } } - return true; + return res; } /** @@ -117,14 +130,15 @@ public class KnowledgeElementTypeServiceImpl implements * @return true if approval succeeded */ @Transactional - public boolean reserve(KnowledgeElementType kelt) { - if (kelt.getState() != ProgressState.inCHECK) - return false; - kelt.setState(ProgressState.inWORK); - if (kelt.isSaved()) { - getKnowledgeElementTypeDAO().update(kelt); + public boolean reserve(final KnowledgeElementType kelt) { + boolean res = (kelt.getState() == ProgressState.inCHECK); + if (res) { + kelt.setState(ProgressState.inWORK); + if (kelt.isSaved()) { + getKnowledgeElementTypeDAO().update(kelt); + } } - return true; + return res; } /** @@ -143,7 +157,7 @@ public class KnowledgeElementTypeServiceImpl implements * the knowledgeElementTypeDAO to set */ public void setKnowledgeElementTypeDAO( - KnowledgeElementTypeDAO knowledgeElementTypeDAO) { + final KnowledgeElementTypeDAO knowledgeElementTypeDAO) { _knowledgeElementTypeDAO = knowledgeElementTypeDAO; } diff --git a/Workspace/Siman-Common/src/org/splat/service/SimulationContextTypeServiceImpl.java b/Workspace/Siman-Common/src/org/splat/service/SimulationContextTypeServiceImpl.java index 410b642..9012aa9 100644 --- a/Workspace/Siman-Common/src/org/splat/service/SimulationContextTypeServiceImpl.java +++ b/Workspace/Siman-Common/src/org/splat/service/SimulationContextTypeServiceImpl.java @@ -10,10 +10,13 @@ package org.splat.service; +import org.hibernate.criterion.Restrictions; +import org.splat.common.properties.MessageKeyEnum; import org.splat.dal.bo.som.ProgressState; import org.splat.dal.bo.som.SimulationContextType; import org.splat.dal.dao.som.SimulationContextTypeDAO; import org.splat.kernel.InvalidPropertyException; +import org.splat.log.AppLogger; import org.splat.service.technical.ProjectSettingsService; import org.springframework.transaction.annotation.Transactional; @@ -25,6 +28,12 @@ import org.springframework.transaction.annotation.Transactional; public class SimulationContextTypeServiceImpl implements SimulationContextTypeService { + /** + * The logger for the service. + */ + public final static AppLogger LOG = AppLogger + .getLogger(SimulationContextTypeServiceImpl.class); + /** * Injected simulation context type DAO. */ @@ -36,11 +45,15 @@ public class SimulationContextTypeServiceImpl implements * @see org.splat.service.SimulationContextService#createType(java.lang.String, org.splat.service.technical.ProjectSettingsService.Step) */ @Transactional - public SimulationContextType createType(String name, - ProjectSettingsService.Step step) throws InvalidPropertyException { - // TODO: Check for duplicate definition - SimulationContextType type = new SimulationContextType(name, step); - getSimulationContextTypeDAO().create(type); + public SimulationContextType createType(final String name, + final ProjectSettingsService.Step step) throws InvalidPropertyException { + SimulationContextType type = getSimulationContextTypeDAO().findByCriteria(Restrictions.eq("name", name)); + if (type == null) { + type = new SimulationContextType(name, step); + getSimulationContextTypeDAO().create(type); + } else { + LOG.info(MessageKeyEnum.SCT_000001.toString(), name); + } return type; } @@ -52,9 +65,10 @@ public class SimulationContextTypeServiceImpl implements * the type to approve * @return true if approval succeeded */ - public boolean approve(SimulationContextType simCtxType) { - if (simCtxType.getState() != ProgressState.inCHECK) + public boolean approve(final SimulationContextType simCtxType) { + if (simCtxType.getState() != ProgressState.inCHECK) { return false; + } simCtxType.setState(ProgressState.APPROVED); // The type name is supposed being localized getSimulationContextTypeDAO().update(simCtxType); return true; @@ -76,7 +90,7 @@ public class SimulationContextTypeServiceImpl implements * the simulationContextTypeDAO to set */ public void setSimulationContextTypeDAO( - SimulationContextTypeDAO simulationContextTypeDAO) { + final SimulationContextTypeDAO simulationContextTypeDAO) { _simulationContextTypeDAO = simulationContextTypeDAO; } diff --git a/Workspace/Siman-Common/src/test/splat/service/TestScenarioService.java b/Workspace/Siman-Common/src/test/splat/service/TestScenarioService.java index 6b8d6c7..1823de7 100644 --- a/Workspace/Siman-Common/src/test/splat/service/TestScenarioService.java +++ b/Workspace/Siman-Common/src/test/splat/service/TestScenarioService.java @@ -23,6 +23,7 @@ import org.splat.dal.bo.som.DocumentType; import org.splat.dal.bo.som.Publication; import org.splat.dal.bo.som.Scenario; import org.splat.dal.bo.som.Study; +import org.splat.dal.bo.som.Document.Properties; import org.splat.dal.dao.som.Database; import org.splat.dal.dao.som.ScenarioDAO; import org.splat.kernel.InvalidPropertyException; @@ -370,10 +371,11 @@ public class TestScenarioService extends BaseTest { for (FileDTO file : doc.getFiles()) { if (file.getPath().endsWith(format)) { // Create a file in the download directory - String filePath = tmpDir.getAbsolutePath() - + "/" + doc.getTitle() + "_result." + format; + String filePath = tmpDir.getAbsolutePath() + "/" + + doc.getTitle() + "_result." + format; FileWriter fw = new FileWriter(filePath); - fw.write("Simulation of " + format + " file for checkin at " + new Date()); + fw.write("Simulation of " + format + + " file for checkin at " + new Date()); fw.close(); FileDTO fileToCheckin = new FileDTO(filePath); docToCheckin.addFile(fileToCheckin); @@ -442,9 +444,9 @@ public class TestScenarioService extends BaseTest { ht.saveOrUpdate(aStudy); ht.saveOrUpdate(aScenario); + // Create documents for each scenario step Document.Properties dprop = new Document.Properties().setAuthor( anAuthor).setDate(new Date()); - // Create documents int i = 0; for (Step step : steps) { LOG.debug("Create scenario step: " + i); @@ -453,51 +455,22 @@ public class TestScenarioService extends BaseTest { List dtypes = _documentTypeService .selectTypesOf(step); for (DocumentType dtype : dtypes) { + // Create a document published in the scenario + // document: document type[0] - first type used on the step + // .brep + // .med dprop.setName("document" + i++).setType(dtype); if (step.getNumber() > 3) { dprop.setFormat("med"); } else { dprop.setFormat("brep"); } - // Create a document published in the scenario - // document: document type[0] - first type used on the step - // .brep - // .med - Publication pub = _stepService.createDocument(aScStep, dprop); - Assert.assertNotNull(pub.getOwner(), - "The publication must be attached to the scenario."); - Assert.assertEquals(pub.getOwner().getIndex(), aScenario - .getIndex(), - "The publication was not attached to the scenario."); + createDoc(aScenario, aScStep, dprop, "med", false); - aScenario.add(pub); - ht.saveOrUpdate(pub); - - // Attach a file - ht.save(pub.value()); - ht.saveOrUpdate(_publicationService.attach(pub, "med")); - - // Create a document with outdated publication + // Create another document with outdated publication dprop.setName("document" + i++).setType(dtype) .setFormat("brep"); - // Create a document published in the scenario - // document: document type[0] - first type used on the step - // .brep - // .med - pub = _stepService.createDocument(aScStep, dprop); - Assert.assertNotNull(pub.getOwner(), - "The publication must be attached to the scenario."); - Assert.assertEquals(pub.getOwner().getIndex(), aScenario - .getIndex(), - "The publication was not attached to the scenario."); - - pub.setIsnew('O'); - aScenario.add(pub); - ht.saveOrUpdate(pub); - - // Attach a file - ht.save(pub.value()); - ht.saveOrUpdate(_publicationService.attach(pub, "med")); + createDoc(aScenario, aScStep, dprop, "med", true); } if (dtypes.size() <= 0) { @@ -505,6 +478,8 @@ public class TestScenarioService extends BaseTest { } } + // Check that the scenario and its documents have been created correctly. + Assert.assertNotNull(ht.find("from Document"), "No documents in the database."); Assert.assertTrue(ht.find("from Document").size() > 0, @@ -525,7 +500,9 @@ public class TestScenarioService extends BaseTest { "The publication was not attached to the scenario."); } + // Remove the scenario from the current hibernate session. ht.evict(aScenario); + // Check that the scenario is created in the database. Scenario aScen = ht.load(Scenario.class, aScenario.getIndex()); Assert.assertNotNull(aScen, "Scenario was not saved in the database."); Assert.assertTrue(aScen.getDocums().size() > 0, @@ -536,4 +513,56 @@ public class TestScenarioService extends BaseTest { return aScenario.getIndex(); } + + /** + * Create a document published in the scenario.
+ * document:
+ * document type[0] - first type used on the step
+ * <source-file>.brep
+ * <attached-file>.med + * + * @param aScenario + * the scenario to add the document to + * @param aScStep + * scenario step where the document to be published + * @param dprop + * document properties + * @param attachedFileExt + * extension of the secon attached (exported) file + * @param isOutdated + * outdated document flag + * @return the publication of the created document + * @throws IOException + * @throws MultiplyDefinedException + * @throws InvalidPropertyException + * @throws MissedPropertyException + */ + private Publication createDoc(final Scenario aScenario, + final org.splat.som.Step aScStep, final Properties dprop, + final String attachedFileExt, final boolean isOutdated) + throws MissedPropertyException, InvalidPropertyException, + MultiplyDefinedException, IOException { + // Create a document published in the scenario + // document: document type[0] - first type used on the step + // .brep + // .med + Publication pub = _stepService.createDocument(aScStep, dprop); + Assert.assertNotNull(pub.getOwner(), + "The publication must be attached to the scenario."); + Assert.assertEquals(pub.getOwner().getIndex(), aScenario.getIndex(), + "The publication was not attached to the scenario."); + + if (isOutdated) { + pub.setIsnew('O'); + } + aScenario.add(pub); + HibernateTemplate ht = getHibernateTemplate(); + ht.saveOrUpdate(pub); + + // Attach a file + ht.save(pub.value()); + ht.saveOrUpdate(_publicationService.attach(pub, attachedFileExt)); + + return pub; + } } -- 2.30.2