diff --git a/pom.xml b/pom.xml index 4da898d8a4..b6a06f7fde 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ 2025.6.25 2.4.0 - 2025.7.25-6883709e548a1a5b33c7202e + 2025.7.29-6888d7ba4e318f3fad1d864c 3.0.23 diff --git a/step-automation-packages/step-automation-packages-controller/src/main/java/step/automation/packages/AutomationPackageServices.java b/step-automation-packages/step-automation-packages-controller/src/main/java/step/automation/packages/AutomationPackageServices.java index 30c289918f..fd0e6f86f5 100644 --- a/step-automation-packages/step-automation-packages-controller/src/main/java/step/automation/packages/AutomationPackageServices.java +++ b/step-automation-packages/step-automation-packages-controller/src/main/java/step/automation/packages/AutomationPackageServices.java @@ -116,7 +116,7 @@ public String createAutomationPackage(@QueryParam("version") String apVersion, ObjectId id = automationPackageManager.createAutomationPackage( automationPackageInputStream, fileDetail.getFileName(), apVersion, activationExpression, - getObjectEnricher(), getObjectPredicate() + getObjectEnricher(), getObjectPredicate(), getObjectValidator() ); return id == null ? null : id.toString(); } catch (AutomationPackageManagerException e) { @@ -146,7 +146,7 @@ public String createAutomationPackageFromMaven(@QueryParam("version") String apV try { MavenArtifactIdentifier mavenArtifactIdentifier = getMavenArtifactIdentifierFromXml(mavenArtifactXml); return automationPackageManager.createAutomationPackageFromMaven( - mavenArtifactIdentifier, apVersion, activationExpression, getObjectEnricher(), getObjectPredicate() + mavenArtifactIdentifier, apVersion, activationExpression, getObjectEnricher(), getObjectPredicate(), getObjectValidator() ).toString(); } catch (AutomationPackageManagerException e) { throw new ControllerServiceException(e.getMessage()); @@ -192,7 +192,8 @@ public List executeAutomationPackage(@FormDataParam("file") InputStream fileDetail == null ? null : fileDetail.getFileName(), executionParameters, getObjectEnricher(), - getObjectPredicate() + getObjectPredicate(), + getObjectValidator() ); } catch (AutomationPackageManagerException e) { throw new ControllerServiceException(e.getMessage()); @@ -259,7 +260,7 @@ public AutomationPackageUpdateResult updateAutomationPackage(@PathParam("id") St return automationPackageManager.createOrUpdateAutomationPackage( true, false, new ObjectId(id), uploadedInputStream, fileDetail.getFileName(), apVersion, activationExpression, - getObjectEnricher(), getObjectPredicate(), async != null && async + getObjectEnricher(), getObjectPredicate(), getObjectValidator(), async != null && async ); } catch (AutomationPackageManagerException e) { throw new ControllerServiceException(e.getMessage()); @@ -290,7 +291,7 @@ public Response createOrUpdateAutomationPackage(@QueryParam("async") Boolean asy try { AutomationPackageUpdateResult result = automationPackageManager.createOrUpdateAutomationPackage( true, true, null, uploadedInputStream, fileDetail.getFileName(), apVersion, activationExpression, - getObjectEnricher(), getObjectPredicate(), async != null && async + getObjectEnricher(), getObjectPredicate(), getObjectValidator(), async != null && async ); Response.ResponseBuilder responseBuilder; if (result.getStatus() == AutomationPackageUpdateStatus.CREATED) { @@ -326,7 +327,7 @@ public AutomationPackageUpdateResult createOrUpdateAutomationPackageFromMaven(@Q try { MavenArtifactIdentifier mvnIdentifier = getMavenArtifactIdentifierFromXml(mavenArtifactXml); return automationPackageManager.createOrUpdateAutomationPackageFromMaven( - mvnIdentifier, true, true, null, apVersion, activationExpression, getObjectEnricher(), getObjectPredicate(), async == null ? false : async + mvnIdentifier, true, true, null, apVersion, activationExpression, getObjectEnricher(), getObjectPredicate(), getObjectValidator(), async == null ? false : async ); } catch (AutomationPackageManagerException e) { throw new ControllerServiceException(e.getMessage()); @@ -358,7 +359,7 @@ public AutomationPackageUpdateResult updateAutomationPackageFromMaven(@PathParam MavenArtifactIdentifier mvnIdentifier = getMavenArtifactIdentifierFromXml(mavenArtifactXml); return automationPackageManager.createOrUpdateAutomationPackageFromMaven( mvnIdentifier, true, false, new ObjectId(id), apVersion, - activationExpression, getObjectEnricher(), getObjectPredicate(), async == null ? false : async + activationExpression, getObjectEnricher(), getObjectPredicate(), getObjectValidator(), async == null ? false : async ); } catch (AutomationPackageManagerException e) { throw new ControllerServiceException(e.getMessage()); diff --git a/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageManagerOSTest.java b/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageManagerOSTest.java index 534f749867..de699a350d 100644 --- a/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageManagerOSTest.java +++ b/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageManagerOSTest.java @@ -165,7 +165,7 @@ public void testCrud() throws IOException { String fileName = "step-automation-packages-sample1-extended.jar"; File automationPackageJar = new File("src/test/resources/samples/" + fileName); try (InputStream is = new FileInputStream(automationPackageJar)) { - AutomationPackageUpdateResult result = manager.createOrUpdateAutomationPackage(true, true, null, is, fileName, null, null, null, null, false); + AutomationPackageUpdateResult result = manager.createOrUpdateAutomationPackage(true, true, null, is, fileName, null, null, null, null, null, false); Assert.assertEquals(AutomationPackageUpdateStatus.UPDATED, result.getStatus()); ObjectId resultId = result.getId(); @@ -310,7 +310,7 @@ public void testResourcesInKeywordsAndPlans() throws IOException { try (InputStream is = new FileInputStream(automationPackageJar)) { ObjectId result; - result = manager.createAutomationPackage(is, fileName, null, null, null, null); + result = manager.createAutomationPackage(is, fileName, null, null, null, null, null); AutomationPackage storedPackage = automationPackageAccessor.get(result); List storedPlans = planAccessor.findManyByCriteria(getAutomationPackageIdCriteria(result)).collect(Collectors.toList()); @@ -332,7 +332,7 @@ public void testResourcesInKeywordsAndPlans() throws IOException { @Test public void testInvalidFile() throws IOException { try (InputStream is = new FileInputStream("src/test/resources/step/automation/packages/picture.png")) { - manager.createAutomationPackage(is, "picture.png", null, null, null, null); + manager.createAutomationPackage(is, "picture.png", null, null, null, null, null); Assert.fail("The exception should be thrown in case of invalid automation package file"); } catch (AutomationPackageManagerException ex) { // ok - invalid file should cause the exception @@ -343,7 +343,7 @@ public void testInvalidFile() throws IOException { public void testZipArchive() throws IOException { try (InputStream is = new FileInputStream("src/test/resources/step/automation/packages/step-automation-packages.zip")) { ObjectId result; - result = manager.createAutomationPackage(is, "step-automation-packages.zip", null, null, null, null); + result = manager.createAutomationPackage(is, "step-automation-packages.zip", null, null, null, null, null); AutomationPackage storedPackage = automationPackageAccessor.get(result); List storedPlans = planAccessor.findManyByCriteria(getAutomationPackageIdCriteria(result)).collect(Collectors.toList()); @@ -420,9 +420,9 @@ private SampleUploadingResult uploadSample1WithAsserts(boolean createNew, boolea try (InputStream is = new FileInputStream(automationPackageJar)) { ObjectId result; if (createNew) { - result = manager.createAutomationPackage(is, fileName, null, null, null, null); + result = manager.createAutomationPackage(is, fileName, null, null, null, null,null); } else { - AutomationPackageUpdateResult updateResult = manager.createOrUpdateAutomationPackage(true, true, null, is, fileName, null, null, null, null, async); + AutomationPackageUpdateResult updateResult = manager.createOrUpdateAutomationPackage(true, true, null, is, fileName, null, null, null, null, null, async); if (async && expectedDelay) { Assert.assertEquals(AutomationPackageUpdateStatus.UPDATE_DELAYED, updateResult.getStatus()); } else { diff --git a/step-automation-packages/step-automation-packages-junit-core/src/main/java/step/automation/packages/junit/JUnitPlansProvider.java b/step-automation-packages/step-automation-packages-junit-core/src/main/java/step/automation/packages/junit/JUnitPlansProvider.java index 490e80e99f..ffa81d9574 100644 --- a/step-automation-packages/step-automation-packages-junit-core/src/main/java/step/automation/packages/junit/JUnitPlansProvider.java +++ b/step-automation-packages/step-automation-packages-junit-core/src/main/java/step/automation/packages/junit/JUnitPlansProvider.java @@ -47,7 +47,7 @@ public List getTestPlans(ExecutionEngine executionEngine) AutomationPackageFromClassLoaderProvider automationPackageProvider = new AutomationPackageFromClassLoaderProvider(testClass.getClassLoader()); ObjectId automationPackageId = automationPackageManager.createOrUpdateAutomationPackage( false, true, null, automationPackageProvider, null, null, - true, null, null, false + true, null, null, null, false ).getId(); List planFilterList = new ArrayList<>(); diff --git a/step-automation-packages/step-automation-packages-junit5/src/test/java/step/junit5/runner/StepRunnerWithPlansAnnotationTest.java b/step-automation-packages/step-automation-packages-junit5/src/test/java/step/junit5/runner/StepRunnerWithPlansAnnotationTest.java index a318baa90f..6ec5123c29 100644 --- a/step-automation-packages/step-automation-packages-junit5/src/test/java/step/junit5/runner/StepRunnerWithPlansAnnotationTest.java +++ b/step-automation-packages/step-automation-packages-junit5/src/test/java/step/junit5/runner/StepRunnerWithPlansAnnotationTest.java @@ -22,6 +22,10 @@ import step.junit.runners.annotations.ExecutionParameters; import step.junit.runners.annotations.Plans; +/** + * This test covers some plans from annotation package. Plans with JMeter keywords are excluded to support local + * launch without installed JMeter. All plans including JMeter are covered by {@link StepAutomationPackageRunAllTest} + */ @ExcludePlans({"JMeter Plan", "testAutomation.plan"}) @Plans({"plans/plan2.plan", "plans/plan3.plan", "plans/assertsTest.plan"}) @ExecutionParameters({"PARAM_EXEC","Value","PARAM_EXEC2","Value","PARAM_EXEC3","Value"}) diff --git a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageManager.java b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageManager.java index 1aa5422a7c..92f3dcaa07 100644 --- a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageManager.java +++ b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageManager.java @@ -30,10 +30,7 @@ import step.core.collections.IndexField; import step.core.entities.Entity; import step.core.maven.MavenArtifactIdentifier; -import step.core.objectenricher.EnricheableObject; -import step.core.objectenricher.ObjectEnricher; -import step.core.objectenricher.ObjectEnricherComposer; -import step.core.objectenricher.ObjectPredicate; +import step.core.objectenricher.*; import step.core.plans.InMemoryPlanAccessor; import step.core.plans.Plan; import step.core.plans.PlanAccessor; @@ -291,14 +288,15 @@ protected void deleteAutomationPackageEntities(AutomationPackage automationPacka deletePlans(automationPackage); // schedules will be deleted in deleteAdditionalData via hooks deleteResources(automationPackage); - deleteAdditionalData(automationPackage, new AutomationPackageContext(operationMode, resourceManager, null, null,null, extensions)); + // TODO: archive, packageContent, enricher and validator are only used in AutomationPackageContext for save/update - maybe it is better to create separate class AutomationPackageContextForSave rather then use nulls here + deleteAdditionalData(automationPackage, new AutomationPackageContext(operationMode, resourceManager, null, null,null, null, extensions)); } - public ObjectId createAutomationPackageFromMaven(MavenArtifactIdentifier mavenArtifactIdentifier, String apVersion, String activationExpr, ObjectEnricher enricher, ObjectPredicate objectPredicate) { + public ObjectId createAutomationPackageFromMaven(MavenArtifactIdentifier mavenArtifactIdentifier, String apVersion, String activationExpr, ObjectEnricher enricher, ObjectPredicate objectPredicate, ObjectValidator objectValidator) { validateMavenConfigAndArtifactClassifier(mavenArtifactIdentifier); try { try (AutomationPackageFromMavenProvider provider = new AutomationPackageFromMavenProvider(mavenConfigProvider.getConfig(objectPredicate), mavenArtifactIdentifier)) { - return createOrUpdateAutomationPackage(false, true, null, provider, apVersion, activationExpr, false, enricher, objectPredicate, false).getId(); + return createOrUpdateAutomationPackage(false, true, null, provider, apVersion, activationExpr, false, enricher, objectPredicate, objectValidator, false).getId(); } } catch (IOException ex) { throw new AutomationPackageManagerException("Automation package cannot be created. Caused by: " + ex.getMessage(), ex); @@ -332,8 +330,9 @@ protected void validateMavenConfigAndArtifactClassifier(MavenArtifactIdentifier * @return the id of created package * @throws AutomationPackageManagerException */ - public ObjectId createAutomationPackage(InputStream packageStream, String fileName, String apVersion, String activationExpr, ObjectEnricher enricher, ObjectPredicate objectPredicate) throws AutomationPackageManagerException { - return createOrUpdateAutomationPackage(false, true, null, packageStream, fileName, apVersion, activationExpr, enricher, objectPredicate, false).getId(); + public ObjectId createAutomationPackage(InputStream packageStream, String fileName, String apVersion, String activationExpr, + ObjectEnricher enricher, ObjectPredicate objectPredicate, ObjectValidator validator) throws AutomationPackageManagerException { + return createOrUpdateAutomationPackage(false, true, null, packageStream, fileName, apVersion, activationExpr, enricher, objectPredicate, validator, false).getId(); } /** @@ -349,10 +348,11 @@ public ObjectId createAutomationPackage(InputStream packageStream, String fileNa */ public AutomationPackageUpdateResult createOrUpdateAutomationPackage(boolean allowUpdate, boolean allowCreate, ObjectId explicitOldId, InputStream inputStream, String fileName, String apVersion, String activationExpr, - ObjectEnricher enricher, ObjectPredicate objectPredicate, boolean async) throws AutomationPackageManagerException { + ObjectEnricher enricher, ObjectPredicate objectPredicate, ObjectValidator validator, + boolean async) throws AutomationPackageManagerException { try { try (AutomationPackageArchiveProvider provider = new AutomationPackageFromInputStreamProvider(inputStream, fileName)) { - return createOrUpdateAutomationPackage(allowUpdate, allowCreate, explicitOldId, provider, apVersion, activationExpr, false, enricher, objectPredicate, async); + return createOrUpdateAutomationPackage(allowUpdate, allowCreate, explicitOldId, provider, apVersion, activationExpr, false, enricher, objectPredicate, validator, async); } } catch (IOException | AutomationPackageReadingException ex) { throw new AutomationPackageManagerException("Automation package cannot be created. Caused by: " + ex.getMessage(), ex); @@ -372,11 +372,12 @@ public AutomationPackageUpdateResult createOrUpdateAutomationPackage(boolean all public AutomationPackageUpdateResult createOrUpdateAutomationPackageFromMaven(MavenArtifactIdentifier mavenArtifactIdentifier, boolean allowUpdate, boolean allowCreate, ObjectId explicitOldId, String apVersion, String activationExpr, - ObjectEnricher enricher, ObjectPredicate objectPredicate, boolean async) throws AutomationPackageManagerException { + ObjectEnricher enricher, ObjectPredicate objectPredicate, ObjectValidator objectValidator, + boolean async) throws AutomationPackageManagerException { try { validateMavenConfigAndArtifactClassifier(mavenArtifactIdentifier); try (AutomationPackageFromMavenProvider provider = new AutomationPackageFromMavenProvider(mavenConfigProvider.getConfig(objectPredicate), mavenArtifactIdentifier)) { - return createOrUpdateAutomationPackage(allowUpdate, allowCreate, explicitOldId, provider, apVersion, activationExpr, false, enricher, objectPredicate, async); + return createOrUpdateAutomationPackage(allowUpdate, allowCreate, explicitOldId, provider, apVersion, activationExpr, false, enricher, objectPredicate, objectValidator, async); } } catch (IOException ex) { throw new AutomationPackageManagerException("Automation package cannot be created. Caused by: " + ex.getMessage(), ex); @@ -432,7 +433,8 @@ public void updateAutomationPackageMetadata(ObjectId id, String apVersion, Strin */ public AutomationPackageUpdateResult createOrUpdateAutomationPackage(boolean allowUpdate, boolean allowCreate, ObjectId explicitOldId, AutomationPackageArchiveProvider automationPackageProvider, String apVersion, String activationExpr, - boolean isLocalPackage, ObjectEnricher enricher, ObjectPredicate objectPredicate, boolean async) { + boolean isLocalPackage, ObjectEnricher enricher, + ObjectPredicate objectPredicate, ObjectValidator validator, boolean async) { AutomationPackageArchive automationPackageArchive; AutomationPackageContent packageContent; @@ -483,7 +485,7 @@ public AutomationPackageUpdateResult createOrUpdateAutomationPackage(boolean all enrichers.add(new AutomationPackageLinkEnricher(newPackage.getId().toString())); ObjectEnricher enricherForIncludedEntities = ObjectEnricherComposer.compose(enrichers); - fillStaging(staging, packageContent, oldPackage, enricherForIncludedEntities, automationPackageArchive, activationExpr, objectPredicate); + fillStaging(staging, packageContent, oldPackage, enricherForIncludedEntities, validator, automationPackageArchive, activationExpr, objectPredicate); // persist and activate automation package log.debug("Updating automation package, old package is " + ((oldPackage == null) ? "null" : "not null" + ", async: " + async)); @@ -492,7 +494,7 @@ public AutomationPackageUpdateResult createOrUpdateAutomationPackage(boolean all if (oldPackage == null || !async || immediateWriteLock) { //If not async or if it's a new package, we synchronously wait on a write lock and update log.info("Updating the automation package " + newPackage.getId().toString() + " synchronously, any running executions on this package will delay the update."); - ObjectId result = updateAutomationPackage(oldPackage, newPackage, packageContent, staging, enricherForIncludedEntities, immediateWriteLock, automationPackageArchive); + ObjectId result = updateAutomationPackage(oldPackage, newPackage, packageContent, staging, enricherForIncludedEntities, validator, immediateWriteLock, automationPackageArchive); return new AutomationPackageUpdateResult(oldPackage == null ? AutomationPackageUpdateStatus.CREATED : AutomationPackageUpdateStatus.UPDATED, result); } else { // async update @@ -502,7 +504,7 @@ public AutomationPackageUpdateResult createOrUpdateAutomationPackage(boolean all AutomationPackage finalNewPackage = newPackage; delayedUpdateExecutor.submit(() -> { try { - updateAutomationPackage(oldPackage, finalNewPackage, packageContent, staging, enricherForIncludedEntities, false, automationPackageArchive); + updateAutomationPackage(oldPackage, finalNewPackage, packageContent, staging, enricherForIncludedEntities, validator, false, automationPackageArchive); } catch (Exception e) { log.error("Exception on delayed AP update", e); } @@ -533,7 +535,7 @@ public Map> getAllEntities(Obj AutomationPackageHook hook = automationPackageHookRegistry.getHook(hookName); result.putAll(hook.getEntitiesForAutomationPackage( automationPackageId, - new AutomationPackageContext(operationMode, resourceManager, null, null, null, extensions) + new AutomationPackageContext(operationMode, resourceManager, null, null, null, null, extensions) ) ); } @@ -541,7 +543,8 @@ public Map> getAllEntities(Obj } private ObjectId updateAutomationPackage(AutomationPackage oldPackage, AutomationPackage newPackage, - AutomationPackageContent packageContent, AutomationPackageStaging staging, ObjectEnricher enricherForIncludedEntities, + AutomationPackageContent packageContent, AutomationPackageStaging staging, + ObjectEnricher enricherForIncludedEntities, ObjectValidator validator, boolean alreadyLocked, AutomationPackageArchive automationPackageArchive) { try { //If not already locked (i.e. was not able to acquire an immediate write lock) @@ -555,7 +558,7 @@ private ObjectId updateAutomationPackage(AutomationPackage oldPackage, Automatio deleteAutomationPackageEntities(oldPackage); } // persist all staged entities - persistStagedEntities(staging, enricherForIncludedEntities, automationPackageArchive, packageContent); + persistStagedEntities(staging, enricherForIncludedEntities, validator, automationPackageArchive, packageContent); ObjectId result = automationPackageAccessor.save(newPackage).getId(); logAfterSave(staging, oldPackage, newPackage); return result; @@ -603,10 +606,11 @@ protected AutomationPackageStaging createStaging(){ return new AutomationPackageStaging(); } - protected void fillStaging(AutomationPackageStaging staging, AutomationPackageContent packageContent, AutomationPackage oldPackage, ObjectEnricher enricherForIncludedEntities, + protected void fillStaging(AutomationPackageStaging staging, AutomationPackageContent packageContent, AutomationPackage oldPackage, + ObjectEnricher enricherForIncludedEntities, ObjectValidator validatorForIncludedEntities, AutomationPackageArchive automationPackageArchive, String evaluationExpression, ObjectPredicate objectPredicate) { - staging.getPlans().addAll(preparePlansStaging(packageContent, automationPackageArchive, oldPackage, enricherForIncludedEntities, staging.getResourceManager(), evaluationExpression)); - staging.getFunctions().addAll(prepareFunctionsStaging(automationPackageArchive, packageContent, enricherForIncludedEntities, oldPackage, staging.getResourceManager(), evaluationExpression)); + staging.getPlans().addAll(preparePlansStaging(packageContent, automationPackageArchive, oldPackage, enricherForIncludedEntities, validatorForIncludedEntities, staging.getResourceManager(), evaluationExpression)); + staging.getFunctions().addAll(prepareFunctionsStaging(automationPackageArchive, packageContent, enricherForIncludedEntities, validatorForIncludedEntities, oldPackage, staging.getResourceManager(), evaluationExpression)); List hookEntries = new ArrayList<>(); for (String additionalField : packageContent.getAdditionalFields()) { @@ -621,7 +625,7 @@ protected void fillStaging(AutomationPackageStaging staging, AutomationPackageCo try { boolean hooked = automationPackageHookRegistry.onPrepareStaging( hookEntry.fieldName, - new AutomationPackageContext(operationMode, staging.getResourceManager(), automationPackageArchive, packageContent, enricherForIncludedEntities, extensions), + new AutomationPackageContext(operationMode, staging.getResourceManager(), automationPackageArchive, packageContent, enricherForIncludedEntities, validatorForIncludedEntities, extensions), packageContent, hookEntry.values, oldPackage, staging, objectPredicate); @@ -639,6 +643,7 @@ protected void fillStaging(AutomationPackageStaging staging, AutomationPackageCo protected void persistStagedEntities(AutomationPackageStaging staging, ObjectEnricher objectEnricher, + ObjectValidator objectValidator, AutomationPackageArchive automationPackageArchive, AutomationPackageContent packageContent) { List stagingResources = staging.getResourceManager().findManyByCriteria(null); @@ -673,7 +678,7 @@ protected void persistStagedEntities(AutomationPackageStaging staging, try { boolean hooked = automationPackageHookRegistry.onCreate( hookEntry.fieldName, hookEntry.values, - new AutomationPackageContext(operationMode, resourceManager, automationPackageArchive, packageContent, objectEnricher, extensions) + new AutomationPackageContext(operationMode, resourceManager, automationPackageArchive, packageContent, objectEnricher, objectValidator, extensions) ); if (!hooked) { log.warn("Additional field in automation package has been ignored and skipped: " + hookEntry.fieldName); @@ -702,11 +707,11 @@ public void runExtensionsBeforeIsolatedExecution(AutomationPackage automationPac } protected List preparePlansStaging(AutomationPackageContent packageContent, AutomationPackageArchive automationPackageArchive, - AutomationPackage oldPackage, ObjectEnricher enricher, ResourceManager stagingResourceManager, + AutomationPackage oldPackage, ObjectEnricher enricher, ObjectValidator validator, ResourceManager stagingResourceManager, String evaluationExpression) { List plans = packageContent.getPlans(); AutomationPackagePlansAttributesApplier specialAttributesApplier = new AutomationPackagePlansAttributesApplier(stagingResourceManager); - specialAttributesApplier.applySpecialAttributesToPlans(plans, automationPackageArchive, packageContent, enricher, extensions, operationMode); + specialAttributesApplier.applySpecialAttributesToPlans(plans, automationPackageArchive, packageContent, enricher, validator, extensions, operationMode); fillEntities(plans, oldPackage != null ? getPackagePlans(oldPackage.getId()) : new ArrayList<>(), enricher); if (evaluationExpression != null && !evaluationExpression.isEmpty()){ @@ -717,9 +722,10 @@ protected List preparePlansStaging(AutomationPackageContent packageContent return plans; } - protected List prepareFunctionsStaging(AutomationPackageArchive automationPackageArchive, AutomationPackageContent packageContent, ObjectEnricher enricher, + protected List prepareFunctionsStaging(AutomationPackageArchive automationPackageArchive, AutomationPackageContent packageContent, + ObjectEnricher enricher, ObjectValidator validator, AutomationPackage oldPackage, ResourceManager stagingResourceManager, String evaluationExpression) { - AutomationPackageContext apContext = new AutomationPackageContext(operationMode, stagingResourceManager, automationPackageArchive, packageContent, enricher, extensions); + AutomationPackageContext apContext = new AutomationPackageContext(operationMode, stagingResourceManager, automationPackageArchive, packageContent, enricher, validator, extensions); List completeFunctions = packageContent.getKeywords().stream().map(keyword -> keyword.prepareKeyword(apContext)).collect(Collectors.toList()); // get old functions with same name and reuse their ids diff --git a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackagePlansAttributesApplier.java b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackagePlansAttributesApplier.java index fb0c841069..ff70879ac1 100644 --- a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackagePlansAttributesApplier.java +++ b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackagePlansAttributesApplier.java @@ -24,6 +24,7 @@ import step.core.entities.EntityManager; import step.core.entities.EntityReference; import step.core.objectenricher.ObjectEnricher; +import step.core.objectenricher.ObjectValidator; import step.core.plans.Plan; import step.resources.Resource; import step.resources.ResourceManager; @@ -55,15 +56,18 @@ public AutomationPackagePlansAttributesApplier(ResourceManager resourceManager) public void applySpecialAttributesToPlans(List plans, AutomationPackageArchive automationPackageArchive, AutomationPackageContent packageContent, - ObjectEnricher objectEnricher, Map extensions, AutomationPackageOperationMode operationMode) { - AutomationPackageContext apContext = prepareContext(operationMode, automationPackageArchive, packageContent, objectEnricher, extensions); + ObjectEnricher objectEnricher, ObjectValidator validator, + Map extensions, + AutomationPackageOperationMode operationMode) { + AutomationPackageContext apContext = prepareContext(operationMode, automationPackageArchive, packageContent, objectEnricher, validator, extensions); for (Plan plan : plans) { applySpecialValuesForArtifact(plan.getRoot(), apContext); } } - protected AutomationPackageContext prepareContext(AutomationPackageOperationMode operationMode, AutomationPackageArchive automationPackageArchive, AutomationPackageContent packageContent, ObjectEnricher enricher, Map extensions) { - return new AutomationPackageContext(operationMode, resourceManager, automationPackageArchive, packageContent, enricher, extensions); + protected AutomationPackageContext prepareContext(AutomationPackageOperationMode operationMode, AutomationPackageArchive automationPackageArchive, AutomationPackageContent packageContent, + ObjectEnricher enricher, ObjectValidator validator, Map extensions) { + return new AutomationPackageContext(operationMode, resourceManager, automationPackageArchive, packageContent, enricher, validator, extensions); } private void applySpecialValuesForArtifact(AbstractArtefact artifact, AutomationPackageContext apContext) { diff --git a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/execution/AutomationPackageExecutor.java b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/execution/AutomationPackageExecutor.java index dc0c117e12..bae3c3b017 100644 --- a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/execution/AutomationPackageExecutor.java +++ b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/execution/AutomationPackageExecutor.java @@ -30,6 +30,7 @@ import step.core.execution.model.*; import step.core.objectenricher.ObjectEnricher; import step.core.objectenricher.ObjectPredicate; +import step.core.objectenricher.ObjectValidator; import step.core.plans.Plan; import step.core.plans.PlanFilter; import step.core.repositories.RepositoryObjectManager; @@ -89,7 +90,7 @@ public List runDeployedAutomationPackage(ObjectId automationPackageId, } public List runInIsolation(InputStream apInputStream, String inputStreamFileName, IsolatedAutomationPackageExecutionParameters parameters, - ObjectEnricher objectEnricher, ObjectPredicate objectPredicate) { + ObjectEnricher objectEnricher, ObjectPredicate objectPredicate, ObjectValidator validator) { ObjectId contextId = new ObjectId(); List executions = new ArrayList<>(); diff --git a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/execution/RepositoryWithAutomationPackageSupport.java b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/execution/RepositoryWithAutomationPackageSupport.java index 66866a3eaa..87d9f99a1d 100644 --- a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/execution/RepositoryWithAutomationPackageSupport.java +++ b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/execution/RepositoryWithAutomationPackageSupport.java @@ -92,6 +92,8 @@ public TestSetStatusOverview getTestSetStatusOverview(Map reposi PackageExecutionContext ctx = null; try { File artifact = getArtifact(repositoryParameters, objectPredicate); + + // we don't pass the ObjectValidator here, because it is only required to validate the entity before persist into DB ctx = createIsolatedPackageExecutionContext(null, objectPredicate, new ObjectId().toString(), new AutomationPackageFile(artifact, null), false); TestSetStatusOverview overview = new TestSetStatusOverview(); List runs = getFilteredPackagePlans(ctx.getAutomationPackage(), repositoryParameters, ctx.getAutomationPackageManager()) @@ -301,7 +303,7 @@ public PackageExecutionContext createIsolatedPackageExecutionContext(ObjectEnric // create single automation package in isolated manager try (FileInputStream fis = new FileInputStream(apFile.getFile())) { // the apVersion is null (we always use the actual version), because we only create the isolated in-memory AP here - inMemoryPackageManager.createAutomationPackage(fis, apFile.getFile().getName(), null, null, enricher, predicate); + inMemoryPackageManager.createAutomationPackage(fis, apFile.getFile().getName(), null, null, enricher, predicate, null); } catch (IOException e) { throw new AutomationPackageManagerException("Cannot read the AP file: " + apFile.getFile().getName()); } diff --git a/step-cli/step-cli-launcher/src/main/java/step/cli/ApLocalExecuteCommandHandler.java b/step-cli/step-cli-launcher/src/main/java/step/cli/ApLocalExecuteCommandHandler.java index 37bee80614..6ca7c12519 100644 --- a/step-cli/step-cli-launcher/src/main/java/step/cli/ApLocalExecuteCommandHandler.java +++ b/step-cli/step-cli-launcher/src/main/java/step/cli/ApLocalExecuteCommandHandler.java @@ -62,7 +62,7 @@ public void afterExecutionEnd(ExecutionContext context) { AutomationPackageFromInputStreamProvider automationPackageProvider = new AutomationPackageFromInputStreamProvider(is, apFile.getName()); ObjectId automationPackageId = automationPackageManager.createOrUpdateAutomationPackage( false, true, null, automationPackageProvider, null, null, - true, null, null, false + true, null, null, null, false ).getId(); PlanFilter planFilters = getPlanFilters(includePlans, excludePlans, includeCategories, excludeCategories); diff --git a/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java b/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java index 1c2aa9a971..5171216712 100644 --- a/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java +++ b/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java @@ -57,6 +57,7 @@ import step.core.plans.PlanEntity; import step.core.plans.builder.PlanBuilder; import step.datapool.excel.ExcelDataPool; +import step.encryption.AbstractEncryptedValuesManager; import step.expressions.ExpressionHandler; import step.functions.Function; import step.functions.accessor.FunctionAccessor; @@ -72,7 +73,8 @@ import step.planbuilder.FunctionArtefacts; import step.plugins.functions.types.CompositeFunction; import step.plugins.functions.types.CompositeFunctionType; -import step.plugins.parametermanager.ParameterManagerControllerPlugin; +import step.plugins.encryption.EncryptedEntityImportBiConsumer; +import step.plugins.encryption.EncryptedEntityExportBiConsumer; import step.resources.*; import java.io.File; @@ -88,6 +90,13 @@ public class ExportManagerTest { + private static String EXPORT_ENCRYPT_WARN = "The parameter list contains encrypted parameters. The values of these parameters will be reset if you import them on an other installation of step."; + private static String EXPORT_PROTECT_WARN = "The parameter list contains protected parameter. The values of these parameters won't be exported and will have to be reset at import."; + + private static String IMPORT_DECRYPT_FAIL_WARN = "The export file contains encrypted parameter which could not be decrypted. The values of these parameters will be reset."; + private static String IMPORT_DECRYPT_NO_EM_WARN = "The export file contains encrypted parameters. The values of these parameters will be reset."; + private static String IMPORT_RESET_WARN = "The export file contains protected parameters. Their values must be reset."; + private PlanAccessor planAccessor; private EntityManager entityManager; private MigrationManager migrationManager; @@ -150,8 +159,28 @@ private void newContext(EncryptionManager encryptionManager) { .register(new ResourceEntity(resourceAccessor, resourceManager, fileResolver, entityManager)) .register(new Entity<>(EntityManager.resourceRevisions, resourceRevisionAccessor, ResourceRevision.class)); - entityManager.registerExportHook(new ParameterManagerControllerPlugin.ParameterExportBiConsumer()); - entityManager.registerImportHook(new ParameterManagerControllerPlugin.ParameterImportBiConsumer(encryptionManager)); + entityManager.registerExportHook(new EncryptedEntityExportBiConsumer(Parameter.class, "parameter") { + @Override + protected Object getValue(Parameter obj) { + return obj.getValue(); + } + + @Override + protected void setResetValue(Parameter obj) { + obj.setValue(new DynamicValue<>(AbstractEncryptedValuesManager.RESET_VALUE)); + } + }); + entityManager.registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, Parameter.class, "parameter") { + @Override + protected Object getValue(Parameter obj) { + return obj.getValue(); + } + + @Override + protected void setResetValue(Parameter obj) { + obj.setValue(new DynamicValue<>(AbstractEncryptedValuesManager.RESET_VALUE)); + } + }); entityManager.registerImportHook(new ResourceImporter(resourceManager)); migrationManager = new MigrationManager(); @@ -300,8 +329,8 @@ public void testExportAllPlansWithParameters() throws Exception { ExportResult exportResult = exportManager.exportAll(exportConfig); assertEquals(2,exportResult.getMessages().size()); - assertEquals(ParameterManagerControllerPlugin.EXPORT_PROTECT_PARAM_WARN,exportResult.getMessages().toArray()[1]); - assertEquals(ParameterManagerControllerPlugin.EXPORT_ENCRYPT_PARAM_WARN,exportResult.getMessages().toArray()[0]); + assertEquals(EXPORT_PROTECT_WARN,exportResult.getMessages().toArray()[1]); + assertEquals(EXPORT_ENCRYPT_WARN,exportResult.getMessages().toArray()[0]); EncryptionManager encryptionManager = new EncryptionManager() { @Override @@ -329,7 +358,7 @@ public boolean isFirstStart() { ImportConfiguration importConfiguration = new ImportConfiguration(testExportFile, dummyObjectEnricher(), null, true); ImportResult importResult = importManager.importAll(importConfiguration); assertEquals(1,importResult.getMessages().size()); - assertEquals(ParameterManagerControllerPlugin.IMPORT_RESET_WARN,importResult.getMessages().toArray()[0]); + assertEquals(IMPORT_RESET_WARN,importResult.getMessages().toArray()[0]); Plan actualPlan = planAccessor.get(plan.getId()); Plan actualPlan2 = planAccessor.get(plan2.getId()); @@ -372,7 +401,7 @@ public void testImportNewEncryptionManager() throws Exception { ExportResult exportResult = exportManager.exportAll(exportConfig); assertEquals(1,exportResult.getMessages().size()); - assertEquals(ParameterManagerControllerPlugin.EXPORT_ENCRYPT_PARAM_WARN,exportResult.getMessages().toArray()[0]); + assertEquals(EXPORT_ENCRYPT_WARN,exportResult.getMessages().toArray()[0]); //Override previous encryption manager to simulate new instance EncryptionManager encryptionManagerNewInstance = new EncryptionManager() { @@ -398,7 +427,7 @@ public boolean isFirstStart() { ImportConfiguration importConfiguration = new ImportConfiguration(testExportFile, dummyObjectEnricher(), null, true); ImportResult importResult = importManager.importAll(importConfiguration); assertEquals(1,importResult.getMessages().size()); - assertEquals(ParameterManagerControllerPlugin.IMPORT_DECRYPT_FAIL_WARN,importResult.getMessages().toArray()[0]); + assertEquals(IMPORT_DECRYPT_FAIL_WARN,importResult.getMessages().toArray()[0]); Parameter actualParamProtectedEncrypted = parameterAccessor.get(savedParamProtectedEncrypted.getId()); assertEquals(savedParamProtectedEncrypted.getId(), actualParamProtectedEncrypted.getId()); @@ -429,7 +458,7 @@ public void testImportNoEncryptionManager() throws Exception { ExportResult exportResult = exportManager.exportAll(exportConfig); assertEquals(1,exportResult.getMessages().size()); - assertEquals(ParameterManagerControllerPlugin.EXPORT_ENCRYPT_PARAM_WARN,exportResult.getMessages().toArray()[0]); + assertEquals(EXPORT_ENCRYPT_WARN,exportResult.getMessages().toArray()[0]); // Create a new context without encryption manager newContext(null); @@ -437,7 +466,7 @@ public void testImportNoEncryptionManager() throws Exception { ImportConfiguration importConfiguration = new ImportConfiguration(testExportFile, dummyObjectEnricher(), null, true); ImportResult importResult = importManager.importAll(importConfiguration); assertEquals(1,importResult.getMessages().size()); - assertEquals(ParameterManagerControllerPlugin.IMPORT_DECRYPT_NO_EM_WARN,importResult.getMessages().toArray()[0]); + assertEquals(IMPORT_DECRYPT_NO_EM_WARN,importResult.getMessages().toArray()[0]); Parameter actualParamProtectedEncrypted = parameterAccessor.get(savedParamProtectedEncrypted.getId()); assertEquals(savedParamProtectedEncrypted.getId(), actualParamProtectedEncrypted.getId()); @@ -466,13 +495,13 @@ public void testImportProtectedToEncrypted() throws Exception { ExportResult exportResult = exportManager.exportAll(exportConfig); assertEquals(1,exportResult.getMessages().size()); - assertEquals(ParameterManagerControllerPlugin.EXPORT_PROTECT_PARAM_WARN,exportResult.getMessages().toArray()[0]); + assertEquals(EXPORT_PROTECT_WARN,exportResult.getMessages().toArray()[0]); ImportManager importManager = createNewContextAndGetImportManager(); ImportConfiguration importConfiguration = new ImportConfiguration(testExportFile, dummyObjectEnricher(), null, true); ImportResult importResult = importManager.importAll(importConfiguration); assertEquals(1,importResult.getMessages().size()); - assertEquals(ParameterManagerControllerPlugin.IMPORT_RESET_WARN,importResult.getMessages().toArray()[0]); + assertEquals(IMPORT_RESET_WARN,importResult.getMessages().toArray()[0]); Parameter actualParamProtectedEncrypted = parameterAccessor.get(savedParamProtected.getId()); assertEquals(savedParamProtected.getId(), actualParamProtectedEncrypted.getId()); diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityExportBiConsumer.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityExportBiConsumer.java new file mode 100644 index 0000000000..a8dd871169 --- /dev/null +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityExportBiConsumer.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.plugins.encryption; + +import step.core.EncryptedTrackedObject; +import step.core.dynamicbeans.DynamicValue; +import step.core.export.ExportContext; +import step.encryption.AbstractEncryptedValuesManager; + +import java.util.function.BiConsumer; + +public abstract class EncryptedEntityExportBiConsumer implements BiConsumer { + + private final Class clazz; + private final String entityNameForLog; + + public EncryptedEntityExportBiConsumer(Class clazz, String entityNameForLog) { + this.clazz = clazz; + this.entityNameForLog = entityNameForLog; + } + + @Override + public void accept(Object object_, ExportContext exportContext) { + if (object_ != null && clazz.isAssignableFrom(object_.getClass())) { + T param = (T) object_; + //if protected and not encrypted, mask value by changing it to reset value + if (param.getProtectedValue() != null && param.getProtectedValue()) { + if (getValue(param) != null) { + setResetValue(param); + exportContext.addMessage(getExportProtectParamWarn()); + } else { + exportContext.addMessage(getExportEncryptParamWarn()); + } + } + } + } + + protected abstract Object getValue(T obj); + + protected abstract void setResetValue(T obj); + + private String getExportProtectParamWarn(){ + return String.format("The %s list contains protected %s. The values of these %ss won't be exported and will have to be reset at import.", entityNameForLog, entityNameForLog, entityNameForLog); + } + + private String getExportEncryptParamWarn(){ + return String.format("The %s list contains encrypted %ss. The values of these %ss will be reset if you import them on an other installation of step.", entityNameForLog, entityNameForLog, entityNameForLog); + } +} diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityImportBiConsumer.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityImportBiConsumer.java new file mode 100644 index 0000000000..14cc12bfa1 --- /dev/null +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityImportBiConsumer.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.plugins.encryption; + +import step.core.EncryptedTrackedObject; +import step.core.dynamicbeans.DynamicValue; +import step.core.encryption.EncryptionManager; +import step.core.encryption.EncryptionManagerException; +import step.core.imports.ImportContext; +import step.encryption.AbstractEncryptedValuesManager; + +import java.util.function.BiConsumer; + +public abstract class EncryptedEntityImportBiConsumer implements BiConsumer { + + private final EncryptionManager encryptionManager; + private final Class clazz; + private final String entityNameForLog; + + public EncryptedEntityImportBiConsumer(EncryptionManager encryptionManager, Class clazz, String entityNameForLog) { + this.encryptionManager = encryptionManager; + this.clazz = clazz; + this.entityNameForLog = entityNameForLog; + } + + @Override + public void accept(Object object_, ImportContext importContext) { + if (object_ != null && clazz.isAssignableFrom(object_.getClass())) { + T param = (T) object_; + //if importing protected and encrypted value + if (param.getProtectedValue() != null && param.getProtectedValue()) { + if (getValue(param) == null) { + //if we have a valid encryption manager and can still decrypt keep encrypted value, else reset + if (encryptionManager != null && param.getEncryptedValue() != null) { + try { + encryptionManager.decrypt(param.getEncryptedValue()); + } catch (EncryptionManagerException e) { + setResetValue(param); + param.setEncryptedValue(null); + importContext.addMessage(getImportDecryptFailWarn()); + } + } else { + setResetValue(param); + param.setEncryptedValue(null); + importContext.addMessage(getImportDecryptNoEmWarn()); + } + } else { + setResetValue(param); + importContext.addMessage(getImportResetWarn()); + } + } + } + } + + protected abstract Object getValue(T obj); + + protected abstract void setResetValue(T obj); + + private String getImportDecryptFailWarn() { + return String.format("The export file contains encrypted %s which could not be decrypted. The values of these %ss will be reset.", entityNameForLog, entityNameForLog); + } + + private String getImportDecryptNoEmWarn() { + return String.format("The export file contains encrypted %ss. The values of these %ss will be reset.", entityNameForLog, entityNameForLog); + } + + private String getImportResetWarn() { + return String.format("The export file contains protected %ss. Their values must be reset.", entityNameForLog); + } +} diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java index 3886c33838..ac8a2e66c1 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java @@ -29,22 +29,21 @@ import step.core.deployment.ObjectHookControllerPlugin; import step.core.dynamicbeans.DynamicValue; import step.core.encryption.EncryptionManager; -import step.core.encryption.EncryptionManagerException; import step.core.entities.Entity; -import step.core.export.ExportContext; -import step.core.imports.ImportContext; import step.core.plugins.AbstractControllerPlugin; import step.core.plugins.Plugin; import step.engine.plugins.ExecutionEnginePlugin; import step.framework.server.tables.Table; import step.framework.server.tables.TableRegistry; +import step.encryption.AbstractEncryptedValuesManager; import step.parameter.Parameter; import step.parameter.ParameterManager; +import step.plugins.encryption.EncryptedEntityExportBiConsumer; +import step.plugins.encryption.EncryptedEntityImportBiConsumer; import step.plugins.encryption.EncryptionManagerDependencyPlugin; import step.plugins.screentemplating.*; import java.util.Set; -import java.util.function.BiConsumer; import java.util.stream.Collectors; import static step.parameter.Parameter.PARAMETER_PROTECTED_VALUE_FIELD; @@ -70,7 +69,7 @@ public void serverStart(GlobalContext context) { context.put("ParameterAccessor", parameterAccessor); context.get(TableRegistry.class).register(ENTITY_PARAMETERS, new Table<>(collection, "param-read", true) - .withResultItemTransformer((p,s) -> ParameterServices.maskProtectedValue(p)) + .withResultItemTransformer((p,s) -> ParameterManager.maskProtectedValue(p)) .withDerivedTableFiltersFactory(lf -> { Set allFilterAttributes = lf.stream().map(Filters::collectFilterAttributes).flatMap(Set::stream).collect(Collectors.toSet()); return allFilterAttributes.contains(PARAMETER_VALUE_FIELD + ".value") ? new Equals(PARAMETER_PROTECTED_VALUE_FIELD, false) : Filters.empty(); @@ -84,8 +83,28 @@ public void serverStart(GlobalContext context) { Parameter.ENTITY_NAME, parameterAccessor, Parameter.class)); - context.getEntityManager().registerExportHook(new ParameterExportBiConsumer()); - context.getEntityManager().registerImportHook(new ParameterImportBiConsumer(encryptionManager)); + context.getEntityManager().registerExportHook(new EncryptedEntityExportBiConsumer(Parameter.class, parameterManager.getEntityNameForLogging()) { + @Override + protected Object getValue(Parameter obj) { + return obj.getValue(); + } + + @Override + protected void setResetValue(Parameter obj) { + obj.setValue(new DynamicValue<>(AbstractEncryptedValuesManager.RESET_VALUE)); + } + }); + context.getEntityManager().registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, Parameter.class, parameterManager.getEntityNameForLogging()) { + @Override + protected Object getValue(Parameter obj) { + return obj.getValue(); + } + + @Override + protected void setResetValue(Parameter obj) { + obj.setValue(new DynamicValue<>(AbstractEncryptedValuesManager.RESET_VALUE)); + } + }); context.getServiceRegistrationCallback().registerService(ParameterServices.class); } @@ -96,11 +115,11 @@ public void initializeData(GlobalContext context) throws Exception { if(encryptionManager != null) { if(encryptionManager.isFirstStart()) { logger.info("First start of the encryption manager. Encrypting all protected parameters..."); - parameterManager.encryptAllParameters(); + parameterManager.encryptAll(); } if(encryptionManager.isKeyPairChanged()) { logger.info("Key pair of encryption manager changed. Resetting all protected parameters..."); - parameterManager.resetAllProtectedParameters(); + parameterManager.resetAllProtectedValues(); } } } @@ -110,68 +129,4 @@ public ExecutionEnginePlugin getExecutionEnginePlugin() { return new ParameterManagerPlugin(parameterManager); } - public static String EXPORT_PROTECT_PARAM_WARN = "The parameter list contains protected parameter. The values of these parameters won't be exported and will have to be reset at import."; - public static String EXPORT_ENCRYPT_PARAM_WARN = "The parameter list contains encrypted parameters. The values of these parameters will be reset if you import them on an other installation of step."; - public static String IMPORT_DECRYPT_FAIL_WARN = "The export file contains encrypted parameter which could not be decrypted. The values of these parameters will be reset."; - public static String IMPORT_DECRYPT_NO_EM_WARN = "The export file contains encrypted parameters. The values of these parameters will be reset."; - public static String IMPORT_RESET_WARN = "The export file contains protected parameters. Their values must be reset."; - - public static class ParameterExportBiConsumer implements BiConsumer { - - @Override - public void accept(Object object_, ExportContext exportContext) { - if (object_ instanceof Parameter) { - Parameter param = (Parameter) object_; - //if protected and not encrypted, mask value by changing it to reset value - if (param.getProtectedValue() != null && param.getProtectedValue()) { - if (param.getValue() != null) { - param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); - exportContext.addMessage(EXPORT_PROTECT_PARAM_WARN); - } else { - exportContext.addMessage(EXPORT_ENCRYPT_PARAM_WARN); - } - } - } - } - } - - public static class ParameterImportBiConsumer implements BiConsumer { - - private final EncryptionManager encryptionManager; - - public ParameterImportBiConsumer(EncryptionManager encryptionManager) { - this.encryptionManager = encryptionManager; - } - - @Override - public void accept(Object object_, ImportContext importContext) { - if (object_ instanceof Parameter) { - Parameter param = (Parameter) object_; - //if importing protected and encrypted value - if (param.getProtectedValue() != null && param.getProtectedValue()) { - if (param.getValue() == null) { - //if we have a valid encryption manager and can still decrypt keep encrypted value, else reset - if (encryptionManager != null && param.getEncryptedValue() != null) { - try { - encryptionManager.decrypt(param.getEncryptedValue()); - } catch (EncryptionManagerException e) { - param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); - param.setEncryptedValue(null); - importContext.addMessage(IMPORT_DECRYPT_FAIL_WARN); - } - } else { - param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); - param.setEncryptedValue(null); - importContext.addMessage(IMPORT_DECRYPT_NO_EM_WARN); - } - } else { - param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); - importContext.addMessage(IMPORT_RESET_WARN); - } - } - } - } - } - - } diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java index ab13e0167c..3921898d16 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java @@ -28,25 +28,19 @@ import step.core.GlobalContext; import step.core.accessors.Accessor; import step.core.deployment.ControllerServiceException; -import step.core.dynamicbeans.DynamicValue; -import step.core.encryption.EncryptionManagerException; +import step.encryption.AbstractEncryptedValuesManager; +import step.encryption.EncryptedValueManagerException; import step.framework.server.access.AuthorizationManager; import step.framework.server.security.Secured; import step.framework.server.security.SecuredContext; -import step.parameter.Parameter; -import step.parameter.ParameterManager; -import step.parameter.ParameterManagerException; -import step.parameter.ParameterScope; +import step.parameter.*; -import java.util.Date; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import static step.parameter.ParameterManager.PROTECTED_VALUE; - @Path("/parameters") @Tag(name = "Parameters") @Tag(name = "Entity=Parameter") @@ -106,8 +100,8 @@ public Parameter save(Parameter newParameter) { private Parameter save(Parameter newParameter, Parameter sourceParameter) { assertRights(newParameter); try { - return maskProtectedValue(parameterManager.save(newParameter, sourceParameter, getSession().getUser().getUsername())); - } catch (ParameterManagerException e) { + return ParameterManager.maskProtectedValue(parameterManager.save(newParameter, sourceParameter, getSession().getUser().getUsername(), getObjectValidator())); + } catch (EncryptedValueManagerException e) { throw new ControllerServiceException(e.getMessage()); } } @@ -124,9 +118,6 @@ protected boolean hasGlobalParamRight() { return authorizationManager.checkRightInContext(getSession(), "param-global-write"); } - protected static boolean isProtected(Parameter oldParameter) { - return oldParameter.getProtectedValue()!=null && oldParameter.getProtectedValue(); - } @Override public Parameter clone(String id) { @@ -150,19 +141,11 @@ public void delete(String id) { @Override public Parameter get(String id) { Parameter parameter = parameterAccessor.get(new ObjectId(id)); - return maskProtectedValue(parameter); - } - - public static Parameter maskProtectedValue(Parameter parameter) { - if(parameter != null && isProtected(parameter) && - !ParameterManager.RESET_VALUE.equals(parameter.getValue())) { - parameter.setValue(new DynamicValue<>(PROTECTED_VALUE)); - } - return parameter; + return ParameterManager.maskProtectedValue(parameter); } protected List maskProtectedValues(Stream stream) { - return stream.map(ParameterServices::maskProtectedValue).collect(Collectors.toList()); + return stream.map(ParameterManager::maskProtectedValue).collect(Collectors.toList()); } @POST @@ -171,7 +154,7 @@ protected List maskProtectedValues(Stream stream) { @Produces(MediaType.APPLICATION_JSON) @Secured(right="{entity}-read") public Parameter getParameterByAttributes(Map attributes) { - return maskProtectedValue(parameterAccessor.findByAttributes(attributes)); + return ParameterManager.maskProtectedValue(parameterAccessor.findByAttributes(attributes)); } @Override diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java new file mode 100644 index 0000000000..dab5357c6b --- /dev/null +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.plugins.projectsettings; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import step.core.AbstractContext; +import step.core.GlobalContext; +import step.core.collections.Collection; +import step.core.collections.Filters; +import step.core.collections.filters.Equals; +import step.core.deployment.ObjectHookControllerPlugin; +import step.core.encryption.EncryptionManager; +import step.core.entities.Entity; +import step.core.objectenricher.*; +import step.core.plugins.AbstractControllerPlugin; +import step.core.plugins.Plugin; +import step.framework.server.tables.Table; +import step.framework.server.tables.TableRegistry; +import step.encryption.AbstractEncryptedValuesManager; +import step.plugins.encryption.EncryptionManagerDependencyPlugin; +import step.plugins.encryption.EncryptedEntityImportBiConsumer; +import step.plugins.encryption.EncryptedEntityExportBiConsumer; +import step.plugins.screentemplating.ScreenTemplatePlugin; +import step.projectsettings.ProjectSetting; +import step.projectsettings.ProjectSettingAccessor; +import step.projectsettings.ProjectSettingAccessorImpl; +import step.projectsettings.ProjectSettingManager; + +import java.util.Set; +import java.util.stream.Collectors; + +import static step.core.EncryptedTrackedObject.PARAMETER_PROTECTED_VALUE_FIELD; +import static step.core.EncryptedTrackedObject.PARAMETER_VALUE_FIELD; + +@Plugin(dependencies= {ObjectHookControllerPlugin.class, ScreenTemplatePlugin.class, EncryptionManagerDependencyPlugin.class}) +public class ProjectSettingControllerPlugin extends AbstractControllerPlugin { + + public static Logger logger = LoggerFactory.getLogger(ProjectSettingControllerPlugin.class); + + private ProjectSettingManager projectSettingManager; + private EncryptionManager encryptionManager; + + @Override + public void serverStart(GlobalContext context) { + // The encryption manager might be null + encryptionManager = context.get(EncryptionManager.class); + + Collection collection = context.getCollectionFactory().getCollection(ProjectSetting.ENTITY_NAME, ProjectSetting.class); + + ProjectSettingAccessor projectSettingAccessor = new ProjectSettingAccessorImpl(collection); + context.put(ProjectSettingAccessor.class, projectSettingAccessor); + + ProjectSettingManager projectSettingManager = new ProjectSettingManager(projectSettingAccessor, encryptionManager, context.getConfiguration()); + context.put(ProjectSettingManager.class, projectSettingManager); + this.projectSettingManager = projectSettingManager; + + context.getEntityManager().register(new Entity<>( + ProjectSetting.ENTITY_NAME, + projectSettingAccessor, + ProjectSetting.class)); + context.getEntityManager().registerExportHook(new EncryptedEntityExportBiConsumer(ProjectSetting.class, projectSettingManager.getEntityNameForLogging()) { + @Override + protected Object getValue(ProjectSetting obj) { + return obj.getValue(); + } + + @Override + protected void setResetValue(ProjectSetting obj) { + obj.setValue(AbstractEncryptedValuesManager.RESET_VALUE); + } + }); + context.getEntityManager().registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, ProjectSetting.class, projectSettingManager.getEntityNameForLogging()) { + @Override + protected Object getValue(ProjectSetting obj) { + return obj.getValue(); + } + + @Override + protected void setResetValue(ProjectSetting obj) { + obj.setValue(AbstractEncryptedValuesManager.RESET_VALUE); + } + }); + + context.getServiceRegistrationCallback().registerService(ProjectSettingServices.class); + } + + @Override + public void initializeData(GlobalContext context) throws Exception { + + if(encryptionManager != null) { + if(encryptionManager.isFirstStart()) { + logger.info("First start of the encryption manager. Encrypting all protected parameters..."); + projectSettingManager.encryptAll(); + } + if(encryptionManager.isKeyPairChanged()) { + logger.info("Key pair of encryption manager changed. Resetting all protected parameters..."); + projectSettingManager.resetAllProtectedValues(); + } + } + } + +} diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java new file mode 100644 index 0000000000..9191663125 --- /dev/null +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.plugins.projectsettings; + +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.PostConstruct; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import org.bson.types.ObjectId; +import step.core.deployment.AbstractStepServices; +import step.core.deployment.ControllerServiceException; +import step.encryption.EncryptedValueManagerException; +import step.framework.server.security.Secured; +import step.framework.server.security.SecuredContext; +import step.projectsettings.ProjectSetting; +import step.projectsettings.ProjectSettingAccessor; +import step.projectsettings.ProjectSettingManager; + +import java.util.List; + +@Path("/project-settings") +@Tag(name = "ProjectSettings") +@Tag(name = "Entity=ProjectSetting") +@SecuredContext(key = "entity", value = "project-settings") +public class ProjectSettingServices extends AbstractStepServices { + + private ProjectSettingManager manager; + private ProjectSettingAccessor accessor; + + public ProjectSettingServices() { + } + + @PostConstruct + public void init() throws Exception { + super.init(); + manager = getContext().require(ProjectSettingManager.class); + accessor = getContext().require(ProjectSettingAccessor.class); + } + + @POST + @Secured(right = "{entity}-write") + public ProjectSetting save(ProjectSetting newSetting) { + assertEntityIsAcceptableInContext(newSetting); + if (newSetting.getKey() == null || newSetting.getKey().isBlank()) { + throw new ControllerServiceException("The parameter's key is mandatory."); + } + + ProjectSetting oldSetting; + if (newSetting.getId() != null) { + oldSetting = accessor.get(newSetting.getId()); + } else { + oldSetting = null; + } + + return save(newSetting, oldSetting); + } + + @DELETE + @Path("/{id}") + @Secured(right = "{entity}-delete") + public void delete(@PathParam("id") String id) { + assertEntityIsAcceptableInContext(accessor.get(id)); + accessor.remove(new ObjectId(id)); + } + + private ProjectSetting save(ProjectSetting newSetting, ProjectSetting sourceSetting) { + try { + ProjectSetting result = manager.save(newSetting, sourceSetting, getSession().getUser().getUsername(), getObjectValidator()); + return ProjectSettingManager.maskProtectedValue(result); + } catch (EncryptedValueManagerException e) { + throw new ControllerServiceException(e.getMessage()); + } + } + + + @GET + @Path("/unique/all") + @Produces(MediaType.APPLICATION_JSON) + @Secured(right = "{entity}-read") + public List getUniqueSettings() { + try { + return manager.getAllSettingsWithUniqueKeys(getObjectFilter()); + } catch (Exception e) { + throw new ControllerServiceException(e.getMessage()); + } + } + + @GET + @Path("/unique/single") + @Produces(MediaType.APPLICATION_JSON) + @Secured(right = "{entity}-read") + public ProjectSetting getUniqueSettingByKey(@QueryParam("key") String key) { + try { + return manager.getUniqueSettingByKey(key, getObjectFilter()); + } catch (Exception e) { + throw new ControllerServiceException(e.getMessage()); + } + } +} diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/unique/UniqueEntityControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/unique/UniqueEntityControllerPlugin.java new file mode 100644 index 0000000000..b554279235 --- /dev/null +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/unique/UniqueEntityControllerPlugin.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.plugins.unique; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import step.core.AbstractContext; +import step.core.GlobalContext; +import step.core.collections.CollectionFactory; +import step.core.deployment.ObjectHookControllerPlugin; +import step.core.objectenricher.*; +import step.core.plugins.AbstractControllerPlugin; +import step.core.plugins.Plugin; +import step.unique.UniqueEntityManager; + +import java.util.Map; + +@Plugin(dependencies= {ObjectHookControllerPlugin.class}) +public class UniqueEntityControllerPlugin extends AbstractControllerPlugin { + + public static Logger logger = LoggerFactory.getLogger(UniqueEntityControllerPlugin.class); + + private CollectionFactory collectionFactory; + + @Override + public void serverStart(GlobalContext context) { + + this.collectionFactory = context.getCollectionFactory(); + + UniqueEntityManager uniqueEntityManager = new UniqueEntityManager(); + context.put(UniqueEntityManager.class, uniqueEntityManager); + + context.require(ObjectHookRegistry.class).add(new ObjectHook() { + @Override + public ObjectFilter getObjectFilter(AbstractContext abstractContext) { + return null; + } + + @Override + public ObjectEnricher getObjectEnricher(AbstractContext abstractContext) { + return null; + } + + @Override + public void rebuildContext(AbstractContext abstractContext, EnricheableObject enricheableObject) throws Exception { + + } + + @Override + public boolean isObjectAcceptableInContext(AbstractContext abstractContext, EnricheableObject enricheableObject) { + return true; + } + + @Override + public ObjectValidator getObjectValidator(AbstractContext context, Map config) { + return uniqueEntityManager.createObjectValidator(collectionFactory, config); + } + }); + } + +} diff --git a/step-controller/step-controller-base-plugins/src/test/java/step/plugins/parametermanager/ParameterManagerTest.java b/step-controller/step-controller-base-plugins/src/test/java/step/plugins/parametermanager/ParameterManagerTest.java index c2dc1499d4..a8f8f8910c 100644 --- a/step-controller/step-controller-base-plugins/src/test/java/step/plugins/parametermanager/ParameterManagerTest.java +++ b/step-controller/step-controller-base-plugins/src/test/java/step/plugins/parametermanager/ParameterManagerTest.java @@ -94,12 +94,12 @@ public void test1Common(Configuration configuration) throws ScriptException { Map bindings = new HashMap(); bindings.put("user", "poire"); - Map params = m.getAllParameterValues(bindings, null); + Map params = m.getAllValues(bindings, null); Assert.assertEquals(params.get("key1"),"poirier"); Assert.assertEquals(params.get("key2"),"defaultValue2"); Assert.assertEquals(params.get("key3"),"value3"); - params = m.getAllParameterValues(bindings, t -> false); + params = m.getAllValues(bindings, t -> false); Assert.assertEquals(0, params.size()); } @@ -125,12 +125,12 @@ public void testPerf() throws ScriptException { bindings.put("user", "user"+nIt); long t1 = System.currentTimeMillis(); - Map params = m.getAllParameterValues(bindings, null); + Map params = m.getAllValues(bindings, null); logger.info("ms:"+(System.currentTimeMillis()-t1)); Assert.assertEquals(params.get("key1"),"value"+nIt); t1 = System.currentTimeMillis(); - params = m.getAllParameterValues(bindings, null); + params = m.getAllValues(bindings, null); logger.info("ms:"+(System.currentTimeMillis()-t1)); Assert.assertEquals(params.get("key1"),"value"+nIt); @@ -161,7 +161,7 @@ public void run() { Random r = new Random(); int userId = r.nextInt(nIt)+1; bindings.put("user", "user"+userId); - Map params = m.getAllParameterValues(bindings, null); + Map params = m.getAllValues(bindings, null); Assert.assertEquals(params.get("key1"),"value"+userId); } } diff --git a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSetting.java b/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSetting.java index b6b3444ed7..c7c642a835 100644 --- a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSetting.java +++ b/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSetting.java @@ -18,9 +18,10 @@ ******************************************************************************/ package step.core.controller; +import step.core.ValueWithKey; import step.core.accessors.AbstractIdentifiableObject; -public class ControllerSetting extends AbstractIdentifiableObject { +public class ControllerSetting extends AbstractIdentifiableObject implements ValueWithKey { protected String key; @@ -36,6 +37,7 @@ public ControllerSetting() { super(); } + @Override public String getKey() { return key; } diff --git a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessor.java b/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessor.java index 99e92a9cd0..f74f00ae20 100644 --- a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessor.java +++ b/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessor.java @@ -1,10 +1,9 @@ package step.core.controller; import step.core.accessors.Accessor; +import step.core.settings.SettingAccessorWithHook; -import java.util.List; - -public interface ControllerSettingAccessor extends Accessor { +public interface ControllerSettingAccessor extends Accessor, SettingAccessorWithHook { ControllerSetting getSettingByKey(String key); @@ -16,8 +15,5 @@ public interface ControllerSettingAccessor extends Accessor { ControllerSetting createSettingIfNotExisting(String settingSchedulerEnabled, String string); - void addHook(String key, ControllerSettingHook hook); - - boolean removeHook(String key, ControllerSettingHook hook); } diff --git a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessorImpl.java b/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessorImpl.java index d939721d28..e2f730b53d 100644 --- a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessorImpl.java +++ b/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessorImpl.java @@ -18,239 +18,15 @@ ******************************************************************************/ package step.core.controller; -import org.bson.types.ObjectId; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import step.core.accessors.AbstractAccessor; import step.core.collections.Collection; -import step.core.collections.Filters; +import step.core.settings.AbstractSettingAccessorWithHook; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - -public class ControllerSettingAccessorImpl extends AbstractAccessor implements ControllerSettingAccessor { - - private static final Logger log = LoggerFactory.getLogger(ControllerSettingAccessorImpl.class); - - private final Map> hooksMap = new ConcurrentHashMap<>(); +public class ControllerSettingAccessorImpl extends AbstractSettingAccessorWithHook implements ControllerSettingAccessor { public ControllerSettingAccessorImpl(Collection collectionDriver) { super(collectionDriver); } - public ControllerSetting getSettingByKey(String key) { - return collectionDriver.find(Filters.equals("key", key), null, null, null, 0).findFirst().orElse(null); - } - - @Override - public void addHook(String key, ControllerSettingHook hook) { - this.hooksMap.computeIfAbsent(key, k -> new ArrayList<>()); - List list = this.hooksMap.get(key); - list.add(hook); - } - - @Override - public boolean removeHook(String key, ControllerSettingHook hook) { - List hooks = getHooksBySettingKey(key); - if (hooks != null) { - return hooks.remove(hook); - } else { - return false; - } - } - - @Override - public ControllerSetting save(ControllerSetting entity) { - // we can change the key of existing setting - in this case we notify hooks about deleted/created setting - ControllerSetting oldValue = getOldValue(entity); - ControllerSetting res = super.save(entity); - - if (oldValueHasAnotherKey(oldValue, entity)) { - callHooksForChangedKey(oldValue); - } - - List hooks = getHooksBySettingKey(entity.getKey()); - if (hooks != null) { - try { - for (ControllerSettingHook hook : hooks) { - callHookOnSettingSave(res, hook, false); - } - } catch (Exception ex) { - rollbackOldValue(res.getId(), oldValue, ex); - - // notify the caller about rollback - throw new ControllerSettingHookRollbackException("Controller setting rollback", ex); - } - } - - return res; - } - - private ControllerSetting getOldValue(ControllerSetting newValue) { - if (newValue.getId() != null) { - return get(newValue.getId()); - } - return null; - } - - private boolean oldValueHasAnotherKey(ControllerSetting oldValue, ControllerSetting newValue) { - if (newValue != null) { - if (oldValue != null) { - return !Objects.equals(oldValue.getKey(), newValue.getKey()); - } - } - return false; - } - - @Override - public void save(Iterable entities) { - List oldValues = new ArrayList<>(); - List oldValuesWithChangedKeys = new ArrayList<>(); - for (ControllerSetting newValue : entities) { - ControllerSetting oldValue = getOldValue(newValue); - if (oldValue != null) { - oldValues.add(oldValue); - if (oldValueHasAnotherKey(oldValue, newValue)) { - oldValuesWithChangedKeys.add(oldValue); - } - } - } - - super.save(entities); - - try { - for (ControllerSetting entity : entities) { - ControllerSetting oldValueWithChangedKey = oldValuesWithChangedKeys.stream().filter(v -> Objects.equals(v.getId(), entity.getId())).findFirst().orElse(null); - - callHooksForChangedKey(oldValueWithChangedKey); - - List hooks = getHooksBySettingKey(entity.getKey()); - if (hooks != null) { - for (ControllerSettingHook hook : hooks) { - callHookOnSettingSave(entity, hook, false); - } - } - } - } catch (Exception ex) { - // rollback save on hook failure - for (ControllerSetting entity : entities) { - try { - rollbackOldValue( - entity.getId(), - oldValues.stream().filter(v -> Objects.equals(v.getId(), entity.getId())).findFirst().orElse(null), - ex - ); - } catch (Exception ex2) { - // just print errors in log during rollback - log.error("Controller setting hook error", ex); - } - } - - // notify the caller about rollback - throw new ControllerSettingHookRollbackException("Controller setting rollback", ex); - } - } - - @Override - public void remove(ObjectId id) { - ControllerSetting toBeDeleted = get(id); - super.remove(id); - List hooks = getHooksBySettingKey(toBeDeleted.getKey()); - - if (hooks != null) { - try { - for (ControllerSettingHook hook : hooks) { - callHookOnSettingRemove(toBeDeleted, hook, false); - } - } catch (Exception ex) { - rollbackOldValue(id, toBeDeleted, ex); - - // notify the caller about rollback - throw new ControllerSettingHookRollbackException("Controller setting rollback", ex); - } - } - } - - protected void rollbackOldValue(ObjectId settingId, ControllerSetting oldValue, Exception ex) { - if (oldValue != null) { - // if some hook fails, we try to revert the operation - super.save(oldValue); - - // notify already called hooks about reverted operation - for (ControllerSettingHook calledHook : getHooksBySettingKey(oldValue.getKey())) { - // ignore errors in this case, because otherwise we can get the infinite error loop - callHookOnSettingSave(oldValue, calledHook, true); - } - } else { - ControllerSetting toBeRemoved = get(settingId); - - if (toBeRemoved != null) { - super.remove(settingId); - - // notify already called hooks about reverted operation - for (ControllerSettingHook calledHook : getHooksBySettingKey(toBeRemoved.getKey())) { - // ignore errors in this case, because otherwise we can get the infinite error loop - callHookOnSettingRemove(toBeRemoved, calledHook, true); - } - } - } - - } - - /** - * Calls the onSettingRemove hooks when key is changed in some controller setting (the value with old key is handled as removed) - */ - protected void callHooksForChangedKey(ControllerSetting oldValueWithChangedKey) { - if (oldValueWithChangedKey != null) { - List hooksOnDelete = getHooksBySettingKey(oldValueWithChangedKey.getKey()); - if (hooksOnDelete != null) { - try { - for (ControllerSettingHook hook : hooksOnDelete) { - callHookOnSettingRemove(oldValueWithChangedKey, hook, false); - } - } catch (Exception ex) { - rollbackOldValue(oldValueWithChangedKey.getId(), oldValueWithChangedKey, ex); - throw ex; - } - } - } - } - - protected void callHookOnSettingRemove(ControllerSetting deletedSetting, ControllerSettingHook hook, boolean ignoreError) { - try { - hook.onSettingRemove(deletedSetting.getId(), deletedSetting); - } catch (Exception ex) { - if (ignoreError) { - log.error("Controller setting hook error", ex); - } else { - throw ex; - } - } - } - - protected void callHookOnSettingSave(ControllerSetting res, ControllerSettingHook hook, boolean ignoreError) { - try { - hook.onSettingSave(res); - } catch (Exception ex) { - if (ignoreError) { - log.error("Controller setting hook error", ex); - } else { - throw ex; - } - } - } - - protected List getHooksBySettingKey(String settingKey) { - return this.hooksMap.get(settingKey); - } - - protected Map> getHooksMap() { - return hooksMap; - } - // TODO: the following methods should be moved to a ControllerSettingManager. // They actually don't belong to an accessor which role should be limited to // retrieval and persistence of data diff --git a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingHook.java b/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingHook.java deleted file mode 100644 index cf8c905366..0000000000 --- a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingHook.java +++ /dev/null @@ -1,8 +0,0 @@ -package step.core.controller; - -import org.bson.types.ObjectId; - -public interface ControllerSettingHook { - void onSettingSave(ControllerSetting setting); - void onSettingRemove(ObjectId settingId, ControllerSetting removed); -} diff --git a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingHookRollbackException.java b/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingHookRollbackException.java deleted file mode 100644 index 4f2ee46641..0000000000 --- a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingHookRollbackException.java +++ /dev/null @@ -1,11 +0,0 @@ -package step.core.controller; - -public class ControllerSettingHookRollbackException extends RuntimeException { - public ControllerSettingHookRollbackException(String message) { - super(message); - } - - public ControllerSettingHookRollbackException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/step-controller/step-controller-server/src/main/java/step/core/controller/InMemoryControllerSettingAccessor.java b/step-controller/step-controller-server/src/main/java/step/core/controller/InMemoryControllerSettingAccessor.java index 611b4a63bf..5848a0697b 100644 --- a/step-controller/step-controller-server/src/main/java/step/core/controller/InMemoryControllerSettingAccessor.java +++ b/step-controller/step-controller-server/src/main/java/step/core/controller/InMemoryControllerSettingAccessor.java @@ -5,7 +5,7 @@ public class InMemoryControllerSettingAccessor extends ControllerSettingAccessorImpl implements ControllerSettingAccessor { public InMemoryControllerSettingAccessor() { - super(new InMemoryCollection()); + super(new InMemoryCollection<>()); } protected ControllerSetting copy(ControllerSetting controllerSetting){ diff --git a/step-controller/step-controller-server/src/main/java/step/core/deployment/AbstractStepServices.java b/step-controller/step-controller-server/src/main/java/step/core/deployment/AbstractStepServices.java index 8becded49f..8c1bb3dbb5 100644 --- a/step-controller/step-controller-server/src/main/java/step/core/deployment/AbstractStepServices.java +++ b/step-controller/step-controller-server/src/main/java/step/core/deployment/AbstractStepServices.java @@ -71,6 +71,10 @@ protected ObjectEnricher getObjectEnricher() { return objectHookRegistry.getObjectEnricher(getSession()); } + protected ObjectValidator getObjectValidator(){ + return objectHookRegistry.getObjectValidator(getSession()); + } + protected ObjectFilter getObjectFilter() { return objectHookRegistry.getObjectFilter(getSession()); } diff --git a/step-controller/step-controller-server/src/main/java/step/core/scheduler/housekeeping/HousekeepingJobsManager.java b/step-controller/step-controller-server/src/main/java/step/core/scheduler/housekeeping/HousekeepingJobsManager.java index 0ca34ddaa6..6362c03d80 100644 --- a/step-controller/step-controller-server/src/main/java/step/core/scheduler/housekeeping/HousekeepingJobsManager.java +++ b/step-controller/step-controller-server/src/main/java/step/core/scheduler/housekeeping/HousekeepingJobsManager.java @@ -8,8 +8,8 @@ import org.slf4j.LoggerFactory; import step.core.controller.ControllerSetting; import step.core.controller.ControllerSettingAccessor; -import step.core.controller.ControllerSettingHook; -import step.core.controller.ControllerSettingHookRollbackException; +import step.core.settings.SettingHook; +import step.core.settings.SettingHookRollbackException; import java.util.Map; import java.util.Properties; @@ -26,7 +26,7 @@ public class HousekeepingJobsManager { private final ControllerSettingAccessor controllerSettingAccessor; private final HousekeepingJobFactory housekeepingJobFactory; - private final Map hooks = new ConcurrentHashMap<>(); + private final Map hooks = new ConcurrentHashMap<>(); public HousekeepingJobsManager(Configuration configuration, ControllerSettingAccessor controllerSettingAccessor) throws SchedulerException { @@ -47,7 +47,7 @@ public void registerManagedJob(ManagedHousekeepingJob managedJob) throws Schedul this.housekeepingJobFactory.registerJob(managedJob.getJobClass(), managedJob.getJobSupplier()); // when housekeeping cron expression is changed, we want to reschedule a job - ControllerSettingHook hook = new ControllerSettingHook() { + SettingHook hook = new SettingHook<>() { @Override public void onSettingSave(ControllerSetting setting) { try { @@ -57,7 +57,7 @@ public void onSettingSave(ControllerSetting setting) { log.error("Cannot reschedule a housekeeping job. The controller setting won't be changed", ex); // this will cause the rollback in ControllerSettingAccessor - setting won't be changed - throw new ControllerSettingHookRollbackException("Unable to schedule the housekeeping job", ex); + throw new SettingHookRollbackException("Unable to schedule the housekeeping job", ex); } } @@ -70,7 +70,7 @@ public void onSettingRemove(ObjectId id, ControllerSetting deleted) { log.error("Cannot unschedule a housekeeping job. The controller setting won't be changed"); // this will cause the rollback in ControllerSettingAccessor - setting won't be changed - throw new ControllerSettingHookRollbackException("Unable to unschedule the housekeeping job", ex); + throw new SettingHookRollbackException("Unable to unschedule the housekeeping job", ex); } } @@ -141,7 +141,7 @@ private void unscheduleJob(ManagedHousekeepingJob job) throws SchedulerException } public void shutdown() throws SchedulerException { - for (Map.Entry hookEntry : hooks.entrySet()) { + for (Map.Entry hookEntry : hooks.entrySet()) { controllerSettingAccessor.removeHook(hookEntry.getKey(), hookEntry.getValue()); } scheduler.shutdown(); diff --git a/step-controller/step-controller-server/src/test/java/step/core/controller/ControllerSettingAccessorImplTest.java b/step-controller/step-controller-server/src/test/java/step/core/controller/ControllerSettingAccessorImplTest.java index cff6664cb0..96187232ff 100644 --- a/step-controller/step-controller-server/src/test/java/step/core/controller/ControllerSettingAccessorImplTest.java +++ b/step-controller/step-controller-server/src/test/java/step/core/controller/ControllerSettingAccessorImplTest.java @@ -8,6 +8,8 @@ import org.slf4j.LoggerFactory; import step.core.collections.Filters; import step.core.collections.inmemory.InMemoryCollection; +import step.core.settings.SettingHook; +import step.core.settings.SettingHookRollbackException; import java.util.*; import java.util.stream.Collectors; @@ -57,7 +59,7 @@ public void testSave() { // both hooks should be called 'on save' and 'on delete', because the rollback operation performs delete for just saved record accessor.save(notSaved); Assert.fail("Exception not thrown"); - } catch (ControllerSettingHookRollbackException ex) { + } catch (SettingHookRollbackException ex) { // ok } @@ -125,8 +127,10 @@ public void testRemove() { // second hook should throw exception on 'bad_value' - this causes operation rollback and the value should NOT be deleted // both hooks should be called 'on save' and 'on delete', because the rollback operation performs compensating save for just deleted record accessor.remove(saved1.getId()); - } catch (ControllerSettingHookRollbackException ex) { + Assert.fail("Exception is missing"); + } catch (SettingHookRollbackException ex) { // ok + log.info("Settings exception: {}", ex.getMessage()); } // value should not be removed @@ -175,7 +179,7 @@ public void testSaveCollection() { // both hooks should be called 'on save' and 'on delete', because the rollback operation performs delete for just saved record accessor.save(Arrays.asList(notSaved1, notSaved2)); Assert.fail("Exception not thrown"); - } catch (ControllerSettingHookRollbackException ex) { + } catch (SettingHookRollbackException ex) { // ok } @@ -201,7 +205,7 @@ public boolean compareSettings(ControllerSetting a, ControllerSetting b) { return Objects.equals(a.getKey(), b.getKey()) && Objects.equals(a.getValue(), b.getValue()); } - private static class TestHook implements ControllerSettingHook { + private static class TestHook implements SettingHook { protected List hookedOnSave = new ArrayList<>(); protected List hookedOnDelete = new ArrayList<>(); diff --git a/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java b/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java new file mode 100644 index 0000000000..7d72a2fd87 --- /dev/null +++ b/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.core; + +import step.core.accessors.AbstractTrackedObject; +import step.core.objectenricher.EnricheableObject; +import step.parameter.Parameter; + +public abstract class EncryptedTrackedObject extends AbstractTrackedObject implements EnricheableObject, ValueWithKey { + + public static final String PARAMETER_PROTECTED_VALUE_FIELD = "protectedValue"; + public static final String PARAMETER_VALUE_FIELD = "value"; + + protected Boolean protectedValue = false; + + /** + * When running with an encryption manager, the value of protected + * {@link Parameter}s is encrypted and the encrypted value is stored into this + * field + */ + protected String encryptedValue; + protected String key; + + public EncryptedTrackedObject() { + super(); + } + + public Boolean getProtectedValue() { + return protectedValue; + } + + public void setProtectedValue(Boolean protectedValue) { + this.protectedValue = protectedValue; + } + + public String getEncryptedValue() { + return encryptedValue; + } + + public void setEncryptedValue(String encryptedValue) { + this.encryptedValue = encryptedValue; + } + + @Override + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public abstract String getScopeEntity(); +} diff --git a/step-core-model/src/main/java/step/core/ValueWithKey.java b/step-core-model/src/main/java/step/core/ValueWithKey.java new file mode 100644 index 0000000000..9771dd9810 --- /dev/null +++ b/step-core-model/src/main/java/step/core/ValueWithKey.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.core; + +public interface ValueWithKey { + String getKey(); +} diff --git a/step-core-model/src/main/java/step/parameter/Parameter.java b/step-core-model/src/main/java/step/parameter/Parameter.java index 3dfc007cb6..d04e05121e 100644 --- a/step-core-model/src/main/java/step/parameter/Parameter.java +++ b/step-core-model/src/main/java/step/parameter/Parameter.java @@ -20,36 +20,20 @@ import step.commons.activation.ActivableObject; import step.commons.activation.Expression; -import step.core.accessors.AbstractTrackedObject; +import step.core.EncryptedTrackedObject; import step.core.dynamicbeans.DynamicValue; -import step.core.objectenricher.EnricheableObject; -public class Parameter extends AbstractTrackedObject implements ActivableObject, EnricheableObject { +public class Parameter extends EncryptedTrackedObject implements ActivableObject { public static final String ENTITY_NAME = "parameters"; - public static final String PARAMETER_VALUE_FIELD = "value"; - public static final String PARAMETER_PROTECTED_VALUE_FIELD = "protectedValue"; - - protected String key; - - protected DynamicValue value; - protected String description; protected Expression activationExpression; protected Integer priority; - - protected Boolean protectedValue = false; - - /** - * When running with an encryption manager, the value of protected - * {@link Parameter}s is encrypted and the encrypted value is stored into this - * field - */ - protected String encryptedValue; - + protected DynamicValue value; + protected ParameterScope scope; protected String scopeEntity; @@ -65,22 +49,6 @@ public Parameter(Expression activationExpression, String key, String value, Stri this.activationExpression = activationExpression; } - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public DynamicValue getValue() { - return value; - } - - public void setValue(DynamicValue value) { - this.value = value; - } - @Override public Expression getActivationExpression() { return activationExpression; @@ -107,22 +75,6 @@ public void setDescription(String description) { this.description = description; } - public Boolean getProtectedValue() { - return protectedValue; - } - - public void setProtectedValue(Boolean protectedValue) { - this.protectedValue = protectedValue; - } - - public String getEncryptedValue() { - return encryptedValue; - } - - public void setEncryptedValue(String encryptedValue) { - this.encryptedValue = encryptedValue; - } - /** * @return the {@link ParameterScope} of this parameter */ @@ -135,9 +87,10 @@ public void setScope(ParameterScope scope) { } /** - * @return the name of the entity this parameter is restricted to. For instance: if the scope of a Parameter + * @return the name of the entity this parameter is restricted to. For instance: if the scope of a Parameter * is set to FUNCTION, the scopeEntity represent the name of the Function for which this parameter applies */ + @Override public String getScopeEntity() { return scopeEntity; } @@ -146,6 +99,14 @@ public void setScopeEntity(String scopeEntity) { this.scopeEntity = scopeEntity; } + public DynamicValue getValue() { + return value; + } + + public void setValue(DynamicValue value) { + this.value = value; + } + @Override public String toString() { return "Parameter [key=" + key + "]"; diff --git a/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java new file mode 100644 index 0000000000..b18a9d718a --- /dev/null +++ b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.projectsettings; + +import step.core.EncryptedTrackedObject; +import step.unique.EntityWithUniqueAttributes; + +public class ProjectSetting extends EncryptedTrackedObject implements EntityWithUniqueAttributes { + + public static final String ENTITY_NAME = "projectsettings"; + public static final String KEY_FIELD_NAME = "key"; + + protected String value; + protected String description; + + public ProjectSetting() { + super(); + } + + public ProjectSetting(String key, String value, String description) { + super(); + this.value = value; + this.key = key; + this.description = description; + } + + @Override + public String getKey() { + return key; + } + + @Override + public String getKeyFieldName() { + return KEY_FIELD_NAME; + } + + @Override + public String getEntityName() { + return ENTITY_NAME; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Override + public String getScopeEntity() { + return null; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return "ProjectSetting{" + + "value='" + value + '\'' + + ", key='" + key + '\'' + + '}'; + } +} diff --git a/step-core-model/src/main/java/step/unique/EntityWithUniqueAttributes.java b/step-core-model/src/main/java/step/unique/EntityWithUniqueAttributes.java new file mode 100644 index 0000000000..06e3e6de04 --- /dev/null +++ b/step-core-model/src/main/java/step/unique/EntityWithUniqueAttributes.java @@ -0,0 +1,38 @@ +/* + * ****************************************************************************** + * * Copyright (C) 2020, exense GmbH + * * + * * This file is part of STEP + * * + * * STEP is free software: you can redistribute it and/or modify + * * it under the terms of the GNU Affero General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * STEP 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 Affero General Public License for more details. + * * + * * You should have received a copy of the GNU Affero General Public License + * * along with STEP. If not, see . + * ***************************************************************************** + */ +package step.unique; + +/** + * The interface for entities with unique combination of attributes in the application + */ +public interface EntityWithUniqueAttributes { + + /** + * Defines the priority (for instance, in accordance to the tenant) + */ + String ATTRIBUTE_PRIORITY = "priority"; + + String getKey(); + + String getKeyFieldName(); + + String getEntityName(); +} diff --git a/step-core/src/main/java/step/automation/packages/AutomationPackageContext.java b/step-core/src/main/java/step/automation/packages/AutomationPackageContext.java index 5d77abd9e4..7503b50ee5 100644 --- a/step-core/src/main/java/step/automation/packages/AutomationPackageContext.java +++ b/step-core/src/main/java/step/automation/packages/AutomationPackageContext.java @@ -19,6 +19,7 @@ package step.automation.packages; import step.core.objectenricher.ObjectEnricher; +import step.core.objectenricher.ObjectValidator; import step.resources.ResourceManager; import java.util.Map; @@ -33,6 +34,7 @@ public class AutomationPackageContext { private AutomationPackageArchive automationPackageArchive; private AutomationPackageContent packageContent; + private ObjectValidator validator; private ObjectEnricher enricher; private String uploadedPackageFileResource; @@ -42,12 +44,15 @@ public class AutomationPackageContext { public AutomationPackageContext(AutomationPackageOperationMode operationMode, ResourceManager resourceManager, AutomationPackageArchive automationPackageArchive, AutomationPackageContent packageContent, - ObjectEnricher enricher, Map extensions) { + ObjectEnricher enricher, + ObjectValidator validator, + Map extensions) { this.operationMode = Objects.requireNonNull(operationMode); this.resourceManager = resourceManager; this.automationPackageArchive = automationPackageArchive; this.packageContent = packageContent; this.enricher = enricher; + this.validator = validator; this.extensions = extensions; } @@ -75,6 +80,10 @@ public ObjectEnricher getEnricher() { return enricher; } + public ObjectValidator getValidator(){ + return validator; + } + public void setAutomationPackageArchive(AutomationPackageArchive automationPackageArchive) { this.automationPackageArchive = automationPackageArchive; } diff --git a/step-core/src/main/java/step/core/settings/AbstractSettingAccessorWithHook.java b/step-core/src/main/java/step/core/settings/AbstractSettingAccessorWithHook.java new file mode 100644 index 0000000000..6f30db5d24 --- /dev/null +++ b/step-core/src/main/java/step/core/settings/AbstractSettingAccessorWithHook.java @@ -0,0 +1,253 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.core.settings; + +import org.bson.types.ObjectId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import step.core.ValueWithKey; +import step.core.accessors.AbstractAccessor; +import step.core.accessors.AbstractIdentifiableObject; +import step.core.collections.Collection; +import step.core.collections.Filters; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +public abstract class AbstractSettingAccessorWithHook extends AbstractAccessor implements SettingAccessorWithHook { + + protected static final Logger log = LoggerFactory.getLogger(AbstractSettingAccessorWithHook.class); + + private final Map>> hooksMap = new ConcurrentHashMap<>(); + + public AbstractSettingAccessorWithHook(Collection collectionDriver) { + super(collectionDriver); + } + + public T getSettingByKey(String key) { + return collectionDriver.find(Filters.equals("key", key), null, null, null, 0).findFirst().orElse(null); + } + + public void addHook(String key, SettingHook hook) { + this.hooksMap.computeIfAbsent(key, k -> new ArrayList<>()); + List> list = this.hooksMap.get(key); + list.add(hook); + } + + public boolean removeHook(String key, SettingHook hook) { + List> hooks = getHooksBySettingKey(key); + if (hooks != null) { + return hooks.remove(hook); + } else { + return false; + } + } + + @Override + public void remove(ObjectId id) { + T toBeDeleted = get(id); + super.remove(id); + List> hooks = getHooksBySettingKey(toBeDeleted.getKey()); + + if (hooks != null) { + try { + for (SettingHook hook : hooks) { + callHookOnSettingRemove(toBeDeleted, hook, false); + } + } catch (Exception ex) { + rollbackOldValue(id, toBeDeleted, ex); + + // notify the caller about rollback + throw new SettingHookRollbackException("Controller setting rollback", ex); + } + } + } + + protected List> getHooksBySettingKey(String settingKey) { + return this.hooksMap.get(settingKey); + } + + protected Map>> getHooksMap() { + return hooksMap; + } + + protected void callHookOnSettingRemove(T deletedSetting, SettingHook hook, boolean ignoreError) { + try { + hook.onSettingRemove(deletedSetting.getId(), deletedSetting); + } catch (Exception ex) { + if (ignoreError) { + log.error("Controller setting hook error", ex); + } else { + throw ex; + } + } + } + + protected void callHookOnSettingSave(T res, SettingHook hook, boolean ignoreError) { + try { + hook.onSettingSave(res); + } catch (Exception ex) { + if (ignoreError) { + log.error("Controller setting hook error", ex); + } else { + throw ex; + } + } + } + + protected void rollbackOldValue(ObjectId settingId, T oldValue, Exception ex) { + if (oldValue != null) { + // if some hook fails, we try to revert the operation + super.save(oldValue); + + // notify already called hooks about reverted operation + for (SettingHook calledHook : getHooksBySettingKey(oldValue.getKey())) { + // ignore errors in this case, because otherwise we can get the infinite error loop + callHookOnSettingSave(oldValue, calledHook, true); + } + } else { + T toBeRemoved = get(settingId); + + if (toBeRemoved != null) { + super.remove(settingId); + + // notify already called hooks about reverted operation + for (SettingHook calledHook : getHooksBySettingKey(toBeRemoved.getKey())) { + // ignore errors in this case, because otherwise we can get the infinite error loop + callHookOnSettingRemove(toBeRemoved, calledHook, true); + } + } + } + + } + + /** + * Calls the onSettingRemove hooks when key is changed in some controller setting (the value with old key is handled as removed) + */ + protected void callHooksForChangedKey(T oldValueWithChangedKey) { + if (oldValueWithChangedKey != null) { + List> hooksOnDelete = getHooksBySettingKey(oldValueWithChangedKey.getKey()); + if (hooksOnDelete != null) { + try { + for (SettingHook hook : hooksOnDelete) { + callHookOnSettingRemove(oldValueWithChangedKey, hook, false); + } + } catch (Exception ex) { + rollbackOldValue(oldValueWithChangedKey.getId(), oldValueWithChangedKey, ex); + throw ex; + } + } + } + } + + protected T getOldValue(T newValue) { + if (newValue.getId() != null) { + return get(newValue.getId()); + } + return null; + } + + protected boolean oldValueHasAnotherKey(T oldValue, T newValue) { + if (newValue != null) { + if (oldValue != null) { + return !Objects.equals(oldValue.getKey(), newValue.getKey()); + } + } + return false; + } + + @Override + public void save(Iterable entities) { + List oldValues = new ArrayList<>(); + List oldValuesWithChangedKeys = new ArrayList<>(); + for (T newValue : entities) { + T oldValue = getOldValue(newValue); + if (oldValue != null) { + oldValues.add(oldValue); + if (oldValueHasAnotherKey(oldValue, newValue)) { + oldValuesWithChangedKeys.add(oldValue); + } + } + } + + super.save(entities); + + try { + for (T entity : entities) { + T oldValueWithChangedKey = oldValuesWithChangedKeys.stream().filter(v -> Objects.equals(v.getId(), entity.getId())).findFirst().orElse(null); + + callHooksForChangedKey(oldValueWithChangedKey); + + List> hooks = getHooksBySettingKey(entity.getKey()); + if (hooks != null) { + for (SettingHook hook : hooks) { + callHookOnSettingSave(entity, hook, false); + } + } + } + } catch (Exception ex) { + // rollback save on hook failure + for (T entity : entities) { + try { + rollbackOldValue( + entity.getId(), + oldValues.stream().filter(v -> Objects.equals(v.getId(), entity.getId())).findFirst().orElse(null), + ex + ); + } catch (Exception ex2) { + // just print errors in log during rollback + log.error("Controller setting hook error", ex); + } + } + + // notify the caller about rollback + throw new SettingHookRollbackException("Controller setting rollback", ex); + } + } + + @Override + public T save(T entity) { + // we can change the key of existing setting - in this case we notify hooks about deleted/created setting + T oldValue = getOldValue(entity); + T res = super.save(entity); + + if (oldValueHasAnotherKey(oldValue, entity)) { + callHooksForChangedKey(oldValue); + } + + List> hooks = getHooksBySettingKey(entity.getKey()); + if (hooks != null) { + try { + for (SettingHook hook : hooks) { + callHookOnSettingSave(res, hook, false); + } + } catch (Exception ex) { + rollbackOldValue(res.getId(), oldValue, ex); + + // notify the caller about rollback + throw new SettingHookRollbackException("Controller setting rollback", ex); + } + } + + return res; + } +} diff --git a/step-core/src/main/java/step/core/settings/SettingAccessorWithHook.java b/step-core/src/main/java/step/core/settings/SettingAccessorWithHook.java new file mode 100644 index 0000000000..854c04a331 --- /dev/null +++ b/step-core/src/main/java/step/core/settings/SettingAccessorWithHook.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.core.settings; + +import step.core.ValueWithKey; +import step.core.accessors.AbstractIdentifiableObject; + +public interface SettingAccessorWithHook { + + void addHook(String key, SettingHook hook); + + boolean removeHook(String key, SettingHook hook); + +} diff --git a/step-core/src/main/java/step/core/settings/SettingHook.java b/step-core/src/main/java/step/core/settings/SettingHook.java new file mode 100644 index 0000000000..b2d3468953 --- /dev/null +++ b/step-core/src/main/java/step/core/settings/SettingHook.java @@ -0,0 +1,8 @@ +package step.core.settings; + +import org.bson.types.ObjectId; + +public interface SettingHook { + void onSettingSave(T setting); + void onSettingRemove(ObjectId settingId, T removed); +} diff --git a/step-core/src/main/java/step/core/settings/SettingHookRollbackException.java b/step-core/src/main/java/step/core/settings/SettingHookRollbackException.java new file mode 100644 index 0000000000..fe986d524c --- /dev/null +++ b/step-core/src/main/java/step/core/settings/SettingHookRollbackException.java @@ -0,0 +1,11 @@ +package step.core.settings; + +public class SettingHookRollbackException extends RuntimeException { + public SettingHookRollbackException(String message) { + super(message); + } + + public SettingHookRollbackException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java b/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java new file mode 100644 index 0000000000..ffc9e5a7a3 --- /dev/null +++ b/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java @@ -0,0 +1,150 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.encryption; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import step.core.EncryptedTrackedObject; +import step.core.accessors.Accessor; +import step.core.encryption.EncryptionManager; +import step.core.encryption.EncryptionManagerException; +import step.core.objectenricher.ObjectValidator; + +import java.util.*; + +public abstract class AbstractEncryptedValuesManager { + + protected static Logger logger = LoggerFactory.getLogger(AbstractEncryptedValuesManager.class); + + public static final String RESET_VALUE = "####change me####"; + public static final String PROTECTED_VALUE = "******"; + + protected final EncryptionManager encryptionManager; + protected final String defaultScriptEngine; + + public AbstractEncryptedValuesManager(EncryptionManager encryptionManager, String defaultScriptEngine) { + this.encryptionManager = encryptionManager; + this.defaultScriptEngine = defaultScriptEngine; + } + + public T save(T newObj, T sourceObj, String modificationUser, ObjectValidator objectValidator) { + validateBeforeSave(newObj, objectValidator); + + if (sourceObj != null && isProtected(sourceObj)) { + // protected value should not be changed + newObj.setProtectedValue(true); + // if the protected mask is set as value, reuse source value (i.e. value hasn't been changed) + if (getValue(newObj) != null && !isDynamicValue(newObj) && getStringValue(newObj).equals(PROTECTED_VALUE)) { + setValue(newObj, getValue(sourceObj)); + } + } + + try { + newObj = this.encryptValueIfEncryptionManagerAvailable(newObj); + } catch (EncryptionManagerException e) { + throw new EncryptedValueManagerException("Error while encrypting " + getEntityNameForLogging() + " value", e); + } + + Date lastModificationDate = new Date(); + if (sourceObj == null) { + newObj.setCreationDate(lastModificationDate); + newObj.setCreationUser(modificationUser); + } + newObj.setLastModificationDate(lastModificationDate); + newObj.setLastModificationUser(modificationUser); + + return getAccessor().save(newObj); + } + + protected void validateBeforeSave(T newObj, ObjectValidator objectValidator) { + if (objectValidator != null) { + try { + objectValidator.validateOnSave(newObj); + } catch (Exception e) { + throw new EncryptedValueManagerException("Value cannot be saved: " + e.getMessage(), e); + } + } + } + + protected abstract Accessor getAccessor(); + + public static boolean isProtected(T p) { + return p.getProtectedValue() != null && p.getProtectedValue(); + } + + public T encryptValueIfEncryptionManagerAvailable(T obj) throws EncryptionManagerException { + if(encryptionManager != null) { + if(isProtected(obj)) { + String value = getStringValue(obj); + if(value != null) { + setValue(obj, null); + String encryptedValue = encryptionManager.encrypt(value); + obj.setEncryptedValue(encryptedValue); + } + } + } + return obj; + } + + protected abstract boolean isDynamicValue(T obj); + + protected abstract String getStringValue(T obj); + + protected abstract void setValue(T obj, V value); + + protected abstract V getValue(T obj); + + public void encryptAll() { + getAccessor().getAll().forEachRemaining(p->{ + if(isProtected(p)) { + logger.info("Encrypting " + getEntityNameForLogging() + " " + p); + try { + T encrypted = encryptValueIfEncryptionManagerAvailable(p); + getAccessor().save(encrypted); + } catch (EncryptionManagerException e) { + logger.error("Error while encrypting " + getEntityNameForLogging() + " " + p.getKey()); + } + } + }); + } + + public void resetAllProtectedValues() { + getAccessor().getAll().forEachRemaining(p->{ + if(isProtected(p)) { + logger.info("Resetting " + getEntityNameForLogging() + " " + p); + setValue(p, getResetValue()); + p.setEncryptedValue(null); + getAccessor().save(p); + } + }); + } + + public abstract V getResetValue(); + + public EncryptionManager getEncryptionManager() { + return encryptionManager; + } + + public String getDefaultScriptEngine() { + return defaultScriptEngine; + } + + public abstract String getEntityNameForLogging(); + +} diff --git a/step-core/src/main/java/step/encryption/EncryptedValueManagerException.java b/step-core/src/main/java/step/encryption/EncryptedValueManagerException.java new file mode 100644 index 0000000000..bfb4a83332 --- /dev/null +++ b/step-core/src/main/java/step/encryption/EncryptedValueManagerException.java @@ -0,0 +1,11 @@ +package step.encryption; + +public class EncryptedValueManagerException extends RuntimeException { + public EncryptedValueManagerException(String s) { + super(s); + } + + public EncryptedValueManagerException(String message, Exception e) { + super(message, e); + } +} diff --git a/step-core/src/main/java/step/parameter/ParameterManager.java b/step-core/src/main/java/step/parameter/ParameterManager.java index 0b24197b32..6de781c727 100644 --- a/step-core/src/main/java/step/parameter/ParameterManager.java +++ b/step-core/src/main/java/step/parameter/ParameterManager.java @@ -18,47 +18,39 @@ ******************************************************************************/ package step.parameter; -import java.util.*; -import java.util.stream.Collectors; - -import javax.script.Bindings; -import javax.script.ScriptException; -import javax.script.SimpleBindings; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import ch.exense.commons.app.Configuration; import step.commons.activation.Activator; import step.core.accessors.Accessor; import step.core.dynamicbeans.DynamicBeanResolver; import step.core.dynamicbeans.DynamicValue; import step.core.encryption.EncryptionManager; -import step.core.encryption.EncryptionManagerException; import step.core.objectenricher.ObjectPredicate; +import step.core.objectenricher.ObjectValidator; import step.core.plugins.exceptions.PluginCriticalException; +import step.encryption.AbstractEncryptedValuesManager; +import step.encryption.EncryptedValueManagerException; -public class ParameterManager { - - public static final String RESET_VALUE = "####change me####"; - public static final String PROTECTED_VALUE = "******"; +import javax.script.Bindings; +import javax.script.ScriptException; +import javax.script.SimpleBindings; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; - private static Logger logger = LoggerFactory.getLogger(ParameterManager.class); - - private Accessor parameterAccessor; - private EncryptionManager encryptionManager; +public class ParameterManager extends AbstractEncryptedValuesManager> { - private String defaultScriptEngine; - private final DynamicBeanResolver dynamicBeanResolver; + protected final DynamicBeanResolver dynamicBeanResolver; + private final Accessor parameterAccessor; public ParameterManager(Accessor parameterAccessor, EncryptionManager encryptionManager, Configuration configuration, DynamicBeanResolver dynamicBeanResolver) { this(parameterAccessor, encryptionManager, configuration.getProperty("tec.activator.scriptEngine", Activator.DEFAULT_SCRIPT_ENGINE), dynamicBeanResolver); } public ParameterManager(Accessor parameterAccessor, EncryptionManager encryptionManager, String defaultScriptEngine, DynamicBeanResolver dynamicBeanResolver) { + super(encryptionManager, defaultScriptEngine); this.parameterAccessor = parameterAccessor; - this.encryptionManager = encryptionManager; - this.defaultScriptEngine = defaultScriptEngine; this.dynamicBeanResolver = dynamicBeanResolver; } @@ -66,172 +58,141 @@ public static ParameterManager copy(ParameterManager from, Accessor p return new ParameterManager(parameterAccessor, from.encryptionManager, from.defaultScriptEngine, from.dynamicBeanResolver); } - public Parameter save(Parameter newParameter, Parameter sourceParameter, String modificationUser) { - if (isProtected(newParameter) && newParameter.getValue() != null && newParameter.getValue().isDynamic()) { - throw new ParameterManagerException("Protected parameters do not support values with dynamic expression."); - } + @Override + protected Accessor getAccessor() { + return parameterAccessor; + } - ParameterScope scope = newParameter.getScope(); - if(scope != null && scope.equals(ParameterScope.GLOBAL) && newParameter.getScopeEntity() != null) { - throw new ParameterManagerException("Scope entity cannot be set for parameters with GLOBAL scope."); - } + @Override + protected boolean isDynamicValue(Parameter obj) { + return obj.getValue() != null && obj.getValue().isDynamic(); + } - if(sourceParameter != null && isProtected(sourceParameter)) { - // protected value should not be changed - newParameter.setProtectedValue(true); - // if the protected mask is set as value, reuse source value (i.e. value hasn't been changed) - DynamicValue newParameterValue = newParameter.getValue(); - if(newParameterValue != null && !newParameterValue.isDynamic() && newParameterValue.get().equals(PROTECTED_VALUE)) { - newParameter.setValue(sourceParameter.getValue()); - } - } + @Override + protected String getStringValue(Parameter obj) { + return obj.getValue() == null ? null : obj.getValue().get(); + } + + @Override + protected void setValue(Parameter obj, DynamicValue value) { + obj.setValue(value); + } + + @Override + protected DynamicValue getValue(Parameter obj) { + return obj.getValue(); + } + + @Override + public DynamicValue getResetValue() { + return new DynamicValue<>(RESET_VALUE); + } + + @Override + public String getEntityNameForLogging() { + return "parameter"; + } - try { - newParameter = this.encryptParameterValueIfEncryptionManagerAvailable(newParameter); - } catch (EncryptionManagerException e) { - throw new ParameterManagerException("Error while encrypting parameter value", e); + public Accessor getParameterAccessor() { + return getAccessor(); + } + + @Override + protected void validateBeforeSave(Parameter newObj, ObjectValidator objectValidator) { + super.validateBeforeSave(newObj, objectValidator); + + ParameterScope scope = newObj.getScope(); + if(scope != null && scope.equals(ParameterScope.GLOBAL) && newObj.getScopeEntity() != null) { + throw new EncryptedValueManagerException("Scope entity cannot be set for " + getEntityNameForLogging() + "s with GLOBAL scope."); } - Date lastModificationDate = new Date(); - if (sourceParameter == null) { - newParameter.setCreationDate(lastModificationDate); - newParameter.setCreationUser(modificationUser); + if (isProtected(newObj) && newObj.getValue() != null && newObj.getValue().isDynamic()) { + throw new EncryptedValueManagerException("Protected entity (" + getEntityNameForLogging() + ") do not support values with dynamic expression."); } - newParameter.setLastModificationDate(lastModificationDate); - newParameter.setLastModificationUser(modificationUser); - return parameterAccessor.save(newParameter); } - public Map getAllParameterValues(Map contextBindings, ObjectPredicate objectPredicate) { - return getAllParameters(contextBindings, objectPredicate).entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().value.get())); + public Map getAllValues(Map contextBindings, ObjectPredicate objectPredicate) { + return getAllObjects(contextBindings, objectPredicate).entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getValue().get())); } - - public Map getAllParameters(Map contextBindings, ObjectPredicate objectPredicate) { + + public Map getAllObjects(Map contextBindings, ObjectPredicate objectPredicate) { Map result = new HashMap<>(); Bindings bindings = contextBindings!=null?new SimpleBindings(contextBindings):null; - Map> parameterMap = new HashMap>(); - parameterAccessor.getAll().forEachRemaining(p->{ + Map> objectsMap = new HashMap<>(); + getAccessor().getAll().forEachRemaining(p->{ if(objectPredicate==null || objectPredicate.test(p)) { - List parameters = parameterMap.get(p.key); - if(parameters==null) { - parameters = new ArrayList<>(); - parameterMap.put(p.key, parameters); + List objects = objectsMap.get(p.getKey()); + if(objects==null) { + objects = new ArrayList<>(); + objectsMap.put(p.getKey(), objects); } - parameters.add(p); + objects.add(p); try { Activator.compileActivationExpression(p, defaultScriptEngine); } catch (ScriptException e) { - logger.error("Error while compiling activation expression of parameter "+p, e); + logger.error("Error while compiling activation expression of " + getEntityNameForLogging() + " " + p, e); } } }); - - - for(String key:parameterMap.keySet()) { - List parameters = parameterMap.get(key); - Parameter bestMatch = Activator.findBestMatch(bindings, parameters, defaultScriptEngine); + + + for(String key:objectsMap.keySet()) { + List objects = objectsMap.get(key); + Parameter bestMatch = Activator.findBestMatch(bindings, objects, defaultScriptEngine); if(bestMatch!=null) { result.put(key, bestMatch); } } - resolveAllParameters(result, contextBindings); + resolveAllValues(result, contextBindings); return result; } - private void resolveAllParameters(Map allParameters, Map contextBindings) { - List unresolvedParamKeys = new ArrayList<>(allParameters.keySet()); - List resolvedParamKeys = new ArrayList<>(); + private void resolveAllValues(Map allObjects, Map contextBindings) { + List unresolvedKeys = new ArrayList<>(allObjects.keySet()); + List resolvedKeys = new ArrayList<>(); HashMap bindings = new HashMap<>(contextBindings); int unresolvedCountBeforeIteration; do { - unresolvedCountBeforeIteration = unresolvedParamKeys.size(); - unresolvedParamKeys.forEach(k -> { - Parameter parameter = allParameters.get(k); - Boolean protectedValue = parameter.getProtectedValue(); - boolean isProtected = parameter.getProtectedValue() != null && parameter.getProtectedValue(); - DynamicValue parameterValue = parameter.getValue(); - if (!isProtected && parameterValue != null) { + unresolvedCountBeforeIteration = unresolvedKeys.size(); + unresolvedKeys.forEach(k -> { + Parameter obj = allObjects.get(k); + Boolean protectedValue = obj.getProtectedValue(); + boolean isProtected = isProtected(obj); + if (!isProtected && getValue(obj) != null) { try { - if (parameterValue.isDynamic()) { - dynamicBeanResolver.evaluate(parameter, bindings); + if (isDynamicValue(obj) && dynamicBeanResolver != null) { + dynamicBeanResolver.evaluate(obj, bindings); } - String resolvedValue = parameter.value.get(); //throw an error if evaluation failed + String resolvedValue = getStringValue(obj); //throw an error if evaluation failed bindings.put(k, resolvedValue); - resolvedParamKeys.add(k); + resolvedKeys.add(k); } catch (Exception e) { if (logger.isDebugEnabled()) { - logger.debug("Could not (yet) resolve parameter dynamic value " + parameter); + logger.debug("Could not (yet) resolve " + getEntityNameForLogging() + " dynamic value " + obj); } } } else { - //value is not set or parameter is protected, resolution is skipped - resolvedParamKeys.add(k); + //value is not set or is protected, resolution is skipped + resolvedKeys.add(k); if (logger.isDebugEnabled()) { - logger.debug("Following parameters won't be resolved (null or protected value) " + parameter); + logger.debug("Following won't be resolved (null or protected value) " + obj); } } }); - unresolvedParamKeys.removeAll(resolvedParamKeys); - } while (!unresolvedParamKeys.isEmpty() && unresolvedParamKeys.size() < unresolvedCountBeforeIteration); - if (!unresolvedParamKeys.isEmpty()) { - throw new PluginCriticalException("Error while resolving parameters, following parameters could not be resolved: " + unresolvedParamKeys); + unresolvedKeys.removeAll(resolvedKeys); + } while (!unresolvedKeys.isEmpty() && unresolvedKeys.size() < unresolvedCountBeforeIteration); + if (!unresolvedKeys.isEmpty()) { + throw new PluginCriticalException("Error while resolving " + getEntityNameForLogging() + "s, following " + getEntityNameForLogging() + " s could not be resolved: " + unresolvedKeys); } } - - public void encryptAllParameters() { - parameterAccessor.getAll().forEachRemaining(p->{ - if(isProtected(p)) { - logger.info("Encrypting parameter "+p); - try { - Parameter encryptedParameter = encryptParameterValueIfEncryptionManagerAvailable(p); - parameterAccessor.save(encryptedParameter); - } catch (EncryptionManagerException e) { - logger.error("Error while encrypting parameter "+p.getKey()); - } - } - }); - } - - public void resetAllProtectedParameters() { - parameterAccessor.getAll().forEachRemaining(p->{ - if(isProtected(p)) { - logger.info("Resetting parameter "+p); - p.setValue(new DynamicValue<>(RESET_VALUE)); - p.setEncryptedValue(null); - parameterAccessor.save(p); - } - }); - } - private boolean isProtected(Parameter p) { - return p.getProtectedValue() != null && p.getProtectedValue(); - } - - public Parameter encryptParameterValueIfEncryptionManagerAvailable(Parameter parameter) throws EncryptionManagerException { - if(encryptionManager != null) { - if(isProtected(parameter)) { - DynamicValue value = parameter.getValue(); - if(value != null && value.get() != null) { - parameter.setValue(null); - String encryptedValue = encryptionManager.encrypt(value.get()); - parameter.setEncryptedValue(encryptedValue); - } - } - } - return parameter; - } - public String getDefaultScriptEngine() { - return defaultScriptEngine; - } - - public Accessor getParameterAccessor() { - return parameterAccessor; - } - - public EncryptionManager getEncryptionManager() { - return encryptionManager; + public static Parameter maskProtectedValue(Parameter obj) { + if(obj != null && isProtected(obj) & !RESET_VALUE.equals(obj.getValue().getValue())) { + obj.setValue(new DynamicValue<>(PROTECTED_VALUE)); + } + return obj; } } diff --git a/step-core/src/main/java/step/parameter/ParameterManagerException.java b/step-core/src/main/java/step/parameter/ParameterManagerException.java deleted file mode 100644 index 08f91366ae..0000000000 --- a/step-core/src/main/java/step/parameter/ParameterManagerException.java +++ /dev/null @@ -1,13 +0,0 @@ -package step.parameter; - -import step.core.encryption.EncryptionManagerException; - -public class ParameterManagerException extends RuntimeException { - public ParameterManagerException(String s) { - super(s); - } - - public ParameterManagerException(String message, Exception e) { - super(message, e); - } -} diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingAccessor.java b/step-core/src/main/java/step/projectsettings/ProjectSettingAccessor.java new file mode 100644 index 0000000000..c49ca7473d --- /dev/null +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingAccessor.java @@ -0,0 +1,35 @@ +/* + * ****************************************************************************** + * * Copyright (C) 2020, exense GmbH + * * + * * This file is part of STEP + * * + * * STEP is free software: you can redistribute it and/or modify + * * it under the terms of the GNU Affero General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * STEP 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 Affero General Public License for more details. + * * + * * You should have received a copy of the GNU Affero General Public License + * * along with STEP. If not, see . + * ***************************************************************************** + */ +package step.projectsettings; + +import step.core.accessors.Accessor; +import step.core.objectenricher.ObjectFilter; + +import java.util.List; + +public interface ProjectSettingAccessor extends Accessor { + + List getSettingsWithHighestPriority(ObjectFilter filter); + + ProjectSetting getSettingWithHighestPriority(String key, ObjectFilter filter); + + List getSettingByKey(String key, ObjectFilter objectFilter); +} diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java b/step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java new file mode 100644 index 0000000000..36db005f79 --- /dev/null +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java @@ -0,0 +1,114 @@ +/* + * ****************************************************************************** + * * Copyright (C) 2020, exense GmbH + * * + * * This file is part of STEP + * * + * * STEP is free software: you can redistribute it and/or modify + * * it under the terms of the GNU Affero General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * STEP 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 Affero General Public License for more details. + * * + * * You should have received a copy of the GNU Affero General Public License + * * along with STEP. If not, see . + * ***************************************************************************** + */ +package step.projectsettings; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; +import step.core.collections.Collection; +import step.core.collections.Filter; +import step.core.collections.Filters; +import step.core.objectenricher.ObjectFilter; +import step.core.ql.OQLFilterBuilder; +import step.core.settings.AbstractSettingAccessorWithHook; +import step.unique.EntityWithUniqueAttributes; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ProjectSettingAccessorImpl extends AbstractSettingAccessorWithHook implements ProjectSettingAccessor { + + public ProjectSettingAccessorImpl(Collection collectionDriver) { + super(collectionDriver); + } + + @Override + public List getSettingsWithHighestPriority(ObjectFilter filter) { + Stream allEntities = getCollectionDriver().find(getFilterNotNull(filter), null, null, null, 0); + return filterSettingsByPriority(allEntities); + } + + private static Filter getFilterNotNull(ObjectFilter filter) { + return filter == null ? Filters.empty() : (filter.getOQLFilter() == null ? Filters.empty() : OQLFilterBuilder.getFilter(filter.getOQLFilter())); + } + + @Override + public ProjectSetting getSettingWithHighestPriority(String key, ObjectFilter filter) { + Stream filteredByKey = getSettingsStreamByKey(key, filter); + List filtered = filterSettingsByPriority(filteredByKey); + if (filtered.size() == 0) { + return null; + } else if (filtered.size() > 1) { + throw new RuntimeException("Non unique project setting is detected by key: " + key); + } else { + return filtered.get(0); + } + } + + private Stream getSettingsStreamByKey(String key, ObjectFilter filter) { + Filter additionalFilterNotNull = getFilterNotNull(filter); + Filter filterByKey = Filters.equals(ProjectSetting.KEY_FIELD_NAME, key); + Stream filteredByKey = getCollectionDriver().find(Filters.and(List.of(additionalFilterNotNull, filterByKey)), null, null, null, 0); + return filteredByKey; + } + + @Override + public List getSettingByKey(String key, ObjectFilter objectFilter){ + return getSettingsStreamByKey(key, objectFilter).collect(Collectors.toList()); + } + + private static List filterSettingsByPriority(Stream allEntities) { + // to support multitenancy here we want to filter out settings (defined for global project) overridden in local project + List highestPriorityProjectSettings = new ArrayList<>(); + + ListMultimap groupedByKey = ArrayListMultimap.create(); + allEntities.forEach(e -> { + groupedByKey.put(e.getKey(), e); + }); + + for (Object key : groupedByKey.keySet()) { + List settingsWithTheSameKey = groupedByKey.get(key); + ProjectSetting settingWithHighestPriority = null; + + for (ProjectSetting projectSetting : settingsWithTheSameKey) { + if (settingWithHighestPriority == null) { + settingWithHighestPriority = projectSetting; + } else { + String currentPriority = projectSetting.getAttribute(EntityWithUniqueAttributes.ATTRIBUTE_PRIORITY); + String highestPriority = settingWithHighestPriority.getAttribute(EntityWithUniqueAttributes.ATTRIBUTE_PRIORITY); + if (Objects.equals(highestPriority, currentPriority)) { + throw new RuntimeException("Validation failed. 2 setting with same keys " + key + " with various priorities have been detected"); + } else if (highestPriority == null && currentPriority != null) { + settingWithHighestPriority = projectSetting; + } else if (currentPriority != null && Integer.parseInt(highestPriority) < Integer.parseInt(currentPriority)) { + settingWithHighestPriority = projectSetting; + } + } + } + if (settingWithHighestPriority != null) { + highestPriorityProjectSettings.add(settingWithHighestPriority); + } + } + return highestPriorityProjectSettings; + } +} diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java new file mode 100644 index 0000000000..39d6d05312 --- /dev/null +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.projectsettings; + +import ch.exense.commons.app.Configuration; +import step.commons.activation.Activator; +import step.core.accessors.Accessor; +import step.core.encryption.EncryptionManager; +import step.core.objectenricher.ObjectFilter; +import step.encryption.AbstractEncryptedValuesManager; + +import java.util.*; + +public class ProjectSettingManager extends AbstractEncryptedValuesManager { + + private final ProjectSettingAccessor accessor; + + public ProjectSettingManager(ProjectSettingAccessor accessor, EncryptionManager encryptionManager, Configuration configuration) { + this(accessor, encryptionManager, configuration.getProperty("tec.activator.scriptEngine", Activator.DEFAULT_SCRIPT_ENGINE)); + } + + public ProjectSettingManager(ProjectSettingAccessor accessor, EncryptionManager encryptionManager, String defaultScriptEngine) { + // project settings can't be dynamic, so the dynamic bean resolver is null + super(encryptionManager, defaultScriptEngine); + this.accessor = accessor; + } + + public static ProjectSetting maskProtectedValue(ProjectSetting obj) { + if(obj != null && isProtected(obj) & !RESET_VALUE.equals(obj.getValue())) { + obj.setValue(PROTECTED_VALUE); + } + return obj; + } + + @Override + protected Accessor getAccessor() { + return accessor; + } + + @Override + protected boolean isDynamicValue(ProjectSetting obj) { + return false; + } + + @Override + protected String getStringValue(ProjectSetting obj) { + return obj.getValue(); + } + + @Override + protected void setValue(ProjectSetting obj, String value) { + obj.setValue(value); + } + + @Override + protected String getValue(ProjectSetting obj) { + return obj.getValue(); + } + + @Override + public String getResetValue() { + return RESET_VALUE; + } + + @Override + public String getEntityNameForLogging() { + return "project setting"; + } + + public List getAllSettingsWithUniqueKeys(ObjectFilter additionalFilter) { + return accessor.getSettingsWithHighestPriority(additionalFilter); + } + + public ProjectSetting getUniqueSettingByKey(String key, ObjectFilter additionalFilter) { + return accessor.getSettingWithHighestPriority(key, additionalFilter); + } +} diff --git a/step-core/src/main/java/step/unique/EntityWithUniqueAttributesAccessor.java b/step-core/src/main/java/step/unique/EntityWithUniqueAttributesAccessor.java new file mode 100644 index 0000000000..9687ebaa87 --- /dev/null +++ b/step-core/src/main/java/step/unique/EntityWithUniqueAttributesAccessor.java @@ -0,0 +1,119 @@ +/* + * ****************************************************************************** + * * Copyright (C) 2020, exense GmbH + * * + * * This file is part of STEP + * * + * * STEP is free software: you can redistribute it and/or modify + * * it under the terms of the GNU Affero General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * STEP 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 Affero General Public License for more details. + * * + * * You should have received a copy of the GNU Affero General Public License + * * along with STEP. If not, see . + * ***************************************************************************** + */ +package step.unique; + +import step.core.accessors.AbstractAccessor; +import step.core.accessors.AbstractIdentifiableObject; +import step.core.collections.Collection; +import step.core.collections.Filter; +import step.core.collections.Filters; +import step.core.objectenricher.EnricheableObject; + +import java.util.*; + +public class EntityWithUniqueAttributesAccessor extends AbstractAccessor { + + public EntityWithUniqueAttributesAccessor(Collection collectionDriver) { + super((Collection) collectionDriver); + } + + public Optional findDuplicate(EntityWithUniqueAttributes e, List additionalRestrictions) { + EnricheableObject enricheableObject = (EnricheableObject) e; + + ArrayList attrFilter = new ArrayList<>(); + if (enricheableObject.getAttributes() != null && !enricheableObject.getAttributes().isEmpty()) { + for (Map.Entry entry : enricheableObject.getAttributes().entrySet()) { + attrFilter.add(Filters.equals("attributes." + entry.getKey(), entry.getValue())); + } + } + Optional duplicate = findDuplicate(e, attrFilter); + if (duplicate.isPresent()) { + return duplicate; + } + + // perform additional checks with additional conditions + if (additionalRestrictions != null) { + for (AdditionalUniquenessRestriction additionalRestriction : additionalRestrictions) { + attrFilter = new ArrayList<>(); + + if (enricheableObject.getAttributes() != null && !enricheableObject.getAttributes().isEmpty()) { + for (Map.Entry entry : enricheableObject.getAttributes().entrySet()) { + if (additionalRestriction.getIgnoredAttributes() == null || !additionalRestriction.getIgnoredAttributes().contains(entry.getKey())) { + attrFilter.add(Filters.equals("attributes." + entry.getKey(), entry.getValue())); + } + } + + if (additionalRestriction.getAttributeGroupField() != null) { + attrFilter.add(Filters.equals("attributes." + additionalRestriction.getAttributeGroupField(), additionalRestriction.getAttributeGroupValue())); + } + + duplicate = findDuplicate(e, attrFilter); + if (duplicate.isPresent()) { + return duplicate; + } + } + } + } + + return Optional.empty(); + } + + private Optional findDuplicate(EntityWithUniqueAttributes e, ArrayList attrFilter) { + Filter filterByAttribute = addFilterByKey(e, attrFilter); + + // filter out the entity with the same ID and apply the filter by key again (if getKeyFieldName returns null and the key is resolved dynamically) + return getCollectionDriver().find(filterByAttribute, null, null, null, 0) + .filter(tmp -> !Objects.equals(((AbstractIdentifiableObject) e).getId(), tmp.getId()) && Objects.equals(e.getKey(), ((EntityWithUniqueAttributes) tmp).getKey())) + .findFirst(); + } + + private static Filter addFilterByKey(EntityWithUniqueAttributes e, ArrayList attrFilter) { + Filter filterByAttribute = Filters.and(attrFilter); + if(e.getKeyFieldName() != null){ + attrFilter.add(Filters.equals(e.getKeyFieldName(), e.getKey())); + } + return filterByAttribute; + } + + public static class AdditionalUniquenessRestriction { + private Set ignoredAttributes; + private String attributeGroupField; + private String attributeGroupValue; + + public AdditionalUniquenessRestriction(Set ignoredAttributes, String attributeGroupField, String attributeGroupValue) { + this.ignoredAttributes = ignoredAttributes; + this.attributeGroupField = attributeGroupField; + this.attributeGroupValue = attributeGroupValue; + } + + public Set getIgnoredAttributes() { + return ignoredAttributes; + } + + public String getAttributeGroupField() { + return attributeGroupField; + } + + public String getAttributeGroupValue() { + return attributeGroupValue; + } + } +} diff --git a/step-core/src/main/java/step/unique/UniqueEntityManager.java b/step-core/src/main/java/step/unique/UniqueEntityManager.java new file mode 100644 index 0000000000..635d5ed529 --- /dev/null +++ b/step-core/src/main/java/step/unique/UniqueEntityManager.java @@ -0,0 +1,52 @@ +/* + * ****************************************************************************** + * * Copyright (C) 2020, exense GmbH + * * + * * This file is part of STEP + * * + * * STEP is free software: you can redistribute it and/or modify + * * it under the terms of the GNU Affero General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * STEP 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 Affero General Public License for more details. + * * + * * You should have received a copy of the GNU Affero General Public License + * * along with STEP. If not, see . + * ***************************************************************************** + */ +package step.unique; + +import step.core.accessors.AbstractIdentifiableObject; +import step.core.collections.Collection; +import step.core.collections.CollectionFactory; +import step.core.objectenricher.EnricheableObject; +import step.core.objectenricher.ObjectValidator; + +import java.util.*; + +public class UniqueEntityManager { + + public static final String NON_UNIQUE_ATTRIBUTES_CONFIG = "nonUniqueAttributes"; + + public ObjectValidator createObjectValidator(CollectionFactory collectionFactory, Map config) { + return enricheableObject -> { + if (enricheableObject instanceof EntityWithUniqueAttributes) { + Collection collection = collectionFactory.getCollection(((EntityWithUniqueAttributes) enricheableObject).getEntityName(), enricheableObject.getClass()); + EntityWithUniqueAttributesAccessor accessor = new EntityWithUniqueAttributesAccessor<>(collection); + Object configValue = config.get(NON_UNIQUE_ATTRIBUTES_CONFIG); + List additionalRestrictions = configValue == null ? null : new ArrayList<>((List) configValue); + Optional duplicate = (Optional) accessor.findDuplicate((EntityWithUniqueAttributes) enricheableObject, additionalRestrictions); + if (duplicate.isPresent()) { + throw new RuntimeException(String.format("%s (%s) cannot be saved. Another entity (%s) with the same attributes has been detected", + ((EntityWithUniqueAttributes) enricheableObject).getEntityName(), ((AbstractIdentifiableObject) enricheableObject).getId(), ((AbstractIdentifiableObject) duplicate.get()).getId() + )); + } + + } + }; + } +} diff --git a/step-core/src/test/java/step/projectsettings/ProjectSettingAccessorImplTest.java b/step-core/src/test/java/step/projectsettings/ProjectSettingAccessorImplTest.java new file mode 100644 index 0000000000..970bcc5747 --- /dev/null +++ b/step-core/src/test/java/step/projectsettings/ProjectSettingAccessorImplTest.java @@ -0,0 +1,81 @@ +/* + * ****************************************************************************** + * * Copyright (C) 2020, exense GmbH + * * + * * This file is part of STEP + * * + * * STEP is free software: you can redistribute it and/or modify + * * it under the terms of the GNU Affero General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * STEP 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 Affero General Public License for more details. + * * + * * You should have received a copy of the GNU Affero General Public License + * * along with STEP. If not, see . + * ***************************************************************************** + */ + +package step.projectsettings; + +import junit.framework.TestCase; +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import step.core.collections.inmemory.InMemoryCollection; +import step.unique.EntityWithUniqueAttributes; + +import java.util.List; + +public class ProjectSettingAccessorImplTest extends TestCase { + + private static final Logger log = LoggerFactory.getLogger(ProjectSettingAccessorImplTest.class); + + @Test + public void testGetSettingsWithHighestPriority(){ + ProjectSettingAccessorImpl accessor = new ProjectSettingAccessorImpl(new InMemoryCollection<>()); + prepareTestData(accessor); + + List settingsWithHighestPriority = accessor.getSettingsWithHighestPriority(null); + log.info("Found settings: {}", settingsWithHighestPriority); + + Assert.assertEquals(3, settingsWithHighestPriority.size()); + + ProjectSetting s = settingsWithHighestPriority.stream().filter(ss -> ss.getKey().equals("key1")).findFirst().orElseThrow(); + Assert.assertEquals("value with priority", s.getValue()); + + settingsWithHighestPriority.stream().filter(ss -> ss.getKey().equals("key2")).findFirst().orElseThrow(); + settingsWithHighestPriority.stream().filter(ss -> ss.getKey().equals("key3")).findFirst().orElseThrow(); + } + + @Test + public void testGetSettingWithHighestPriority(){ + ProjectSettingAccessorImpl accessor = new ProjectSettingAccessorImpl(new InMemoryCollection<>()); + prepareTestData(accessor); + ProjectSetting s = accessor.getSettingWithHighestPriority("key1", null); + Assert.assertEquals("value with priority", s.getValue()); + } + + private static void prepareTestData(ProjectSettingAccessorImpl accessor) { + ProjectSetting s1 = new ProjectSetting("key1", "value1", "description"); + s1.addAttribute(EntityWithUniqueAttributes.ATTRIBUTE_PRIORITY, "10"); + + ProjectSetting s2 = new ProjectSetting("key2", "value2", "description"); + s2.addAttribute(EntityWithUniqueAttributes.ATTRIBUTE_PRIORITY, "10"); + + ProjectSetting s3 = new ProjectSetting("key1", "value with priority", "description"); + s3.addAttribute(EntityWithUniqueAttributes.ATTRIBUTE_PRIORITY, "100"); + + // without priority attribute + ProjectSetting s4 = new ProjectSetting("key3", "value3", "description"); + + accessor.save(s1); + accessor.save(s2); + accessor.save(s3); + accessor.save(s4); + } +} \ No newline at end of file diff --git a/step-core/src/test/java/step/unique/UniqueEntityManagerTest.java b/step-core/src/test/java/step/unique/UniqueEntityManagerTest.java new file mode 100644 index 0000000000..334b3d4a40 --- /dev/null +++ b/step-core/src/test/java/step/unique/UniqueEntityManagerTest.java @@ -0,0 +1,89 @@ +/* + * ****************************************************************************** + * * Copyright (C) 2020, exense GmbH + * * + * * This file is part of STEP + * * + * * STEP is free software: you can redistribute it and/or modify + * * it under the terms of the GNU Affero General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * STEP 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 Affero General Public License for more details. + * * + * * You should have received a copy of the GNU Affero General Public License + * * along with STEP. If not, see . + * ***************************************************************************** + */ + +package step.unique; + +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import step.core.collections.Collection; +import step.core.collections.inmemory.InMemoryCollectionFactory; +import step.core.objectenricher.ObjectValidator; +import step.projectsettings.ProjectSetting; + +import java.util.HashMap; + +public class UniqueEntityManagerTest { + private static final Logger log = LoggerFactory.getLogger(UniqueEntityManagerTest.class); + + @Test + public void testObjectValidator(){ + UniqueEntityManager manager = new UniqueEntityManager(); + + InMemoryCollectionFactory inMemoryCollectionFactory = new InMemoryCollectionFactory(null); + Collection projectSettingCollection = inMemoryCollectionFactory.getCollection(ProjectSetting.ENTITY_NAME, ProjectSetting.class); + ProjectSetting ps1 = new ProjectSetting("setting1", "value1", "description1"); + ps1.addAttribute("name", "setting1"); + ps1.addAttribute("project", "project1"); + projectSettingCollection.save(ps1); + + ProjectSetting ps2 = new ProjectSetting("setting2", "value2", "description2"); + ps1.addAttribute("name", "setting2"); + ps1.addAttribute("project", "project2"); + projectSettingCollection.save(ps2); + + ObjectValidator validator = manager.createObjectValidator(inMemoryCollectionFactory, new HashMap<>()); + + // new entity - ok + ProjectSetting newPs = new ProjectSetting("setting3", "value3", "description3"); + newPs.addAttribute("name", "setting3"); + newPs.addAttribute("project", "project_new"); + validator.validateOnSave(newPs); + + // update the same entity (with the same id) - ok + newPs = new ProjectSetting("setting1", "updated value", "updated description"); + newPs.setId(ps1.getId()); + newPs.addAttribute("name", "setting1"); + newPs.addAttribute("project", "project1"); + validator.validateOnSave(newPs); + + // entity with the same key and name, but for another project - ok + newPs = new ProjectSetting("setting1", "updated value", "updated description"); + newPs.addAttribute("name", "setting1"); + newPs.addAttribute("project", "project_new"); + validator.validateOnSave(newPs); + + // collision - the same key, name and project + // entity with the same key and name, but for another project - ok + newPs = new ProjectSetting("setting1", "updated value", "updated description"); + newPs.addAttribute("name", "setting1"); + newPs.addAttribute("project", "project1"); + + try { + validator.validateOnSave(newPs); + Assert.fail("Exception should be thrown here"); + } catch (Exception e) { + log.info("Collision detected - OK: {}", e.getMessage()); + } + + } +} \ No newline at end of file diff --git a/step-plans/step-plans-base-artefacts/src/main/java/step/plugins/parametermanager/ParameterManagerPlugin.java b/step-plans/step-plans-base-artefacts/src/main/java/step/plugins/parametermanager/ParameterManagerPlugin.java index 637060be12..f7e7ccc09f 100644 --- a/step-plans/step-plans-base-artefacts/src/main/java/step/plugins/parametermanager/ParameterManagerPlugin.java +++ b/step-plans/step-plans-base-artefacts/src/main/java/step/plugins/parametermanager/ParameterManagerPlugin.java @@ -102,7 +102,7 @@ public void executionStart(ExecutionContext context) { // Resolve the active parameters Map contextBindings = ExecutionContextBindings.get(context); ObjectPredicate objectPredicate = context.getObjectPredicate(); - Map allParameters = getParameterManagerFromContext(context).getAllParameters(contextBindings, objectPredicate); + Map allParameters = getParameterManagerFromContext(context).getAllObjects(contextBindings, objectPredicate); // Add all the active parameters to the execution parameter map of the Execution object buildExecutionParametersMapAndUpdateExecution(context, allParameters); diff --git a/step-plans/step-plans-core/src/main/java/step/parameter/automation/AutomationPackageParameterHook.java b/step-plans/step-plans-core/src/main/java/step/parameter/automation/AutomationPackageParameterHook.java index 7466a558cd..d1d83934c9 100644 --- a/step-plans/step-plans-core/src/main/java/step/parameter/automation/AutomationPackageParameterHook.java +++ b/step-plans/step-plans-core/src/main/java/step/parameter/automation/AutomationPackageParameterHook.java @@ -31,7 +31,7 @@ import step.core.repositories.ImportResult; import step.parameter.Parameter; import step.parameter.ParameterManager; -import step.parameter.ParameterManagerException; +import step.encryption.EncryptedValueManagerException; import java.util.Iterator; import java.util.List; @@ -74,10 +74,10 @@ public void onCreate(List entities, AutomationPackageContex // enrich with automation package id context.getEnricher().accept(entity); try { - getParameterManager(context.getExtensions()).save(entity, null, null); - } catch (ParameterManagerException e) { + getParameterManager(context.getExtensions()).save(entity, null, null, context.getValidator()); + } catch (EncryptedValueManagerException e) { log.error("The automation package parameter {} cannot be saved for automation package {}.", entity.getKey(), context.getAutomationPackageArchive().getOriginalFileName(), e); - throw new ParameterManagerException("The automation package parameter " + entity.getKey() + " cannot be saved: " + e.getMessage()); + throw new EncryptedValueManagerException("The automation package parameter " + entity.getKey() + " cannot be saved: " + e.getMessage()); } } }