diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 19ca7f13b..36efe668c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,8 @@ # Contributing to Anthem Open Source projects -Want to hack on changes to an Anthem Open Source Project? We have a contributor's guide that explains +Want to hack on changes to the Nimbus Framework? We have a contributor's guide that explains [setting up a development environment and the contribution -process](https://anthemopensource.atlassian.net/wiki/spaces/opensource/pages/45842524/Setup+Development+Environment). +process](https://anthemopensource.atlassian.net/wiki/spaces/OSS/pages/523436073/Contributing). This page contains information about reporting issues as well as some tips and guidelines useful to experienced open source contributors. Finally, make sure @@ -146,7 +146,7 @@ Include an issue reference like `Closes #XXXX` or `Fixes #XXXX` in the pull requ description that close an issue. Including references automatically closes the issue on a merge. -Please see the [Coding Style](#coding-style) for further guidelines. +Please see the [Coding Style](https://anthemopensource.atlassian.net/wiki/spaces/OSS/pages/523698209/Nimbus+Framework+Code+Style) for further guidelines. ### Merge approval @@ -224,7 +224,3 @@ intolerable. * Contact abuse@oss.antheminc.com to report abuse or appeal violations. In the case of appeals, we know that mistakes happen, and we'll work with you to come up with a fair solution if there has been a misunderstanding. - -## Coding Style - -Anthem will define these guidelines soon. diff --git a/README.md b/README.md index 96cf5d506..c21dd508d 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,8 @@ https://anthemopensource.atlassian.net/wiki/spaces/OSS/pages [Nexus Sonatype](https://oss.sonatype.org/#nexus-search;quick~com.antheminc.oss) ## Issue Reporting +Are you stuck on an implementation or do you think you might have found an issue within the framework? +Please create a post in the [OSS Discourse Forum](http://discourse.oss.antheminc.com/) so that we might take a look and validate the issue you're seeing. In the case that a framework change is needed, one of the project admins will create an associated issue in JIRA (Coming soon...) to address the required change. ## Unit Tests diff --git a/nimbus-core/lombok.config b/nimbus-core/lombok.config new file mode 100644 index 000000000..6fe22f91c --- /dev/null +++ b/nimbus-core/lombok.config @@ -0,0 +1,2 @@ +lombok.anyConstructor.addConstructorProperties=true +config.stopBubbling = true \ No newline at end of file diff --git a/nimbus-core/pom.xml b/nimbus-core/pom.xml index f3c6a1b6f..3e81ab0a2 100644 --- a/nimbus-core/pom.xml +++ b/nimbus-core/pom.xml @@ -4,14 +4,14 @@ com.antheminc.oss nimbus-parent - 1.2.0.BUILD-SNAPSHOT + 2.0.0.BUILD-SNAPSHOT nimbus-core target/jacoco.exec, ../nimbus-test/target/jacoco.exec - src/main/java/com/antheminc/oss/nimbus/entity/**/*.java + src/main/java/com/antheminc/oss/nimbus/entity/**/*.java, src/main/java/com/antheminc/oss/nimbus/domain/defn/ViewConfig.java diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/FrameworkRuntimeException.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/FrameworkRuntimeException.java index 13807542c..f42d1cec2 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/FrameworkRuntimeException.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/FrameworkRuntimeException.java @@ -46,12 +46,6 @@ public FrameworkRuntimeException(String message, Throwable cause) { super(message, cause); this.execEx = create(message); } - - public FrameworkRuntimeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - this.execEx = create(message); - } - private ExecuteError create(String msg) { return new ExecuteError(this.getClass(), msg); diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/InvalidArgumentException.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/InvalidArgumentException.java index 423441e36..fca4bbd9c 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/InvalidArgumentException.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/InvalidArgumentException.java @@ -30,16 +30,4 @@ public InvalidArgumentException(String message) { super(message); } - public InvalidArgumentException(Throwable cause) { - super(cause); - } - - public InvalidArgumentException(String message, Throwable cause) { - super(message, cause); - } - - public InvalidArgumentException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } - } diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/InvalidConfigException.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/InvalidConfigException.java index eb91d19a2..efe2fef17 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/InvalidConfigException.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/InvalidConfigException.java @@ -22,26 +22,13 @@ public class InvalidConfigException extends FrameworkRuntimeException { private static final long serialVersionUID = 1L; - - - public InvalidConfigException() { } public InvalidConfigException(String message) { super(message); } - - public InvalidConfigException(Throwable cause) { - super(cause); - } public InvalidConfigException(String message, Throwable cause) { super(message, cause); } - public InvalidConfigException(String message, Throwable cause, boolean enableSuppression, - boolean writableStackTrace) { - - super(message, cause, enableSuppression, writableStackTrace); - } - } diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/InvalidOperationAttemptedException.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/InvalidOperationAttemptedException.java index bd18265b5..a58497408 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/InvalidOperationAttemptedException.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/InvalidOperationAttemptedException.java @@ -22,26 +22,9 @@ public class InvalidOperationAttemptedException extends FrameworkRuntimeException { private static final long serialVersionUID = 1L; - - - public InvalidOperationAttemptedException() { } public InvalidOperationAttemptedException(String message) { super(message); } - - public InvalidOperationAttemptedException(Throwable cause) { - super(cause); - } - - public InvalidOperationAttemptedException(String message, Throwable cause) { - super(message, cause); - } - - public InvalidOperationAttemptedException(String message, Throwable cause, boolean enableSuppression, - boolean writableStackTrace) { - - super(message, cause, enableSuppression, writableStackTrace); - } } diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/JsonConversionException.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/JsonConversionException.java index fa2d36111..99b2cf218 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/JsonConversionException.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/JsonConversionException.java @@ -25,18 +25,6 @@ public class JsonConversionException extends FrameworkRuntimeException { * */ private static final long serialVersionUID = 1L; - - public JsonConversionException() { - super(); - } - - public JsonConversionException(String message) { - super(message); - } - - public JsonConversionException(Throwable cause) { - super(cause); - } public JsonConversionException(String message, Throwable cause) { super(message, cause); diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/UnsupportedScenarioException.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/UnsupportedScenarioException.java index c1567e048..5647f4c1a 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/UnsupportedScenarioException.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/UnsupportedScenarioException.java @@ -23,22 +23,8 @@ public class UnsupportedScenarioException extends FrameworkRuntimeException { private static final long serialVersionUID = 1L; - public UnsupportedScenarioException() {} - public UnsupportedScenarioException(String message) { super(message); } - public UnsupportedScenarioException(Throwable cause) { - super(cause); - } - - public UnsupportedScenarioException(String message, Throwable cause) { - super(message, cause); - } - - public UnsupportedScenarioException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } - } diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/BPMEngineConfig.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/BPMEngineConfig.java index 558eb65b9..86e75d1c1 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/BPMEngineConfig.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/BPMEngineConfig.java @@ -28,6 +28,7 @@ import org.activiti.engine.impl.bpmn.parser.factory.DefaultActivityBehaviorFactory; import org.activiti.engine.impl.history.HistoryLevel; import org.activiti.engine.impl.persistence.deploy.Deployer; +import org.activiti.runtime.api.identity.UserGroupManager; import org.activiti.spring.SpringAsyncExecutor; import org.activiti.spring.SpringProcessEngineConfiguration; import org.activiti.spring.boot.AbstractProcessEngineAutoConfiguration; @@ -115,11 +116,11 @@ public class BPMEngineConfig extends AbstractProcessEngineAutoConfiguration { @Bean public SpringProcessEngineConfiguration springProcessEngineConfiguration( @Qualifier("processDataSource") DataSource processDataSource, - PlatformTransactionManager jpaTransactionManager, SpringAsyncExecutor springAsyncExecutor, - BeanResolverStrategy beanResolver) throws Exception { + PlatformTransactionManager jpaTransactionManager, SpringAsyncExecutor springAsyncExecutor, + BeanResolverStrategy beanResolver, UserGroupManager userGroupManager) throws Exception { SpringProcessEngineConfiguration engineConfiguration = this - .baseSpringProcessEngineConfiguration(processDataSource, jpaTransactionManager, springAsyncExecutor, null); + .baseSpringProcessEngineConfiguration(processDataSource, jpaTransactionManager, springAsyncExecutor,userGroupManager); if (deploymentName.isPresent()) { engineConfiguration.setDeploymentName(deploymentName.get()); diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultCoreBuilderConfig.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultCoreBuilderConfig.java index 3d301520e..0c1d129e8 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultCoreBuilderConfig.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultCoreBuilderConfig.java @@ -39,8 +39,10 @@ import com.antheminc.oss.nimbus.domain.config.builder.AnnotationConfigHandler; import com.antheminc.oss.nimbus.domain.config.builder.DefaultAnnotationConfigHandler; import com.antheminc.oss.nimbus.domain.config.builder.DomainConfigBuilder; +import com.antheminc.oss.nimbus.domain.config.builder.EventAnnotationConfigHandler; import com.antheminc.oss.nimbus.domain.config.builder.attributes.ConstraintAnnotationAttributeHandler; import com.antheminc.oss.nimbus.domain.config.builder.attributes.DefaultAnnotationAttributeHandler; +import com.antheminc.oss.nimbus.domain.model.config.EntityConfig.Scope; import com.antheminc.oss.nimbus.domain.model.config.builder.EntityConfigBuilder; import com.antheminc.oss.nimbus.domain.model.config.builder.internal.DefaultEntityConfigBuilder; import com.antheminc.oss.nimbus.domain.model.config.builder.internal.DefaultExecutionConfigProvider; @@ -74,6 +76,10 @@ public class DefaultCoreBuilderConfig { private List basePackages; + private List basePackagesToExclude; + + private Map> domainSet; + @Value("${platform.config.secure.regex}") private String secureRegex; @@ -90,7 +96,7 @@ public ChangeLogCommandEventHandler changeLogCommandEventHandler(BeanResolverStr @Bean public DomainConfigBuilder domainConfigBuilder(EntityConfigBuilder configBuilder){ - return new DomainConfigBuilder(configBuilder, basePackages); + return new DomainConfigBuilder(configBuilder, basePackages, basePackagesToExclude); } @Bean @@ -133,21 +139,29 @@ public DetourExecutionConfigProvider detourExecutionConfigProvider(BeanResolverS return new DetourExecutionConfigProvider(); } - @Bean - public AnnotationConfigHandler annotationConfigHandler(PropertyResolver propertyResolver) { + @Bean(name="default.annotationConfigBuilder") + public AnnotationConfigHandler annotationConfigHandler(BeanResolverStrategy beanResolver, PropertyResolver propertyResolver) { Map, AnnotationAttributeHandler> attributeHandlers = new HashMap<>(); - attributeHandlers.put(Constraint.class, new ConstraintAnnotationAttributeHandler()); + attributeHandlers.put(Constraint.class, new ConstraintAnnotationAttributeHandler(beanResolver)); return new DefaultAnnotationConfigHandler(new DefaultAnnotationAttributeHandler(), attributeHandlers, propertyResolver); } + @Bean(name="default.eventAnnotationConfigBuilder") + public AnnotationConfigHandler eventAnnotationConfigHandler() { + return new EventAnnotationConfigHandler(); + } + @Bean public EntityConfigBuilder entityConfigBuilder(BeanResolverStrategy beanResolver){ if(typeClassMappings==null) { typeClassMappings = new HashMap<>(); } + if(domainSet == null) { + domainSet = new HashMap<>(); + } - return new DefaultEntityConfigBuilder(beanResolver, typeClassMappings); + return new DefaultEntityConfigBuilder(beanResolver, typeClassMappings, domainSet); } @Bean diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultCoreConfiguration.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultCoreConfiguration.java index 2dac20f26..4f654f66e 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultCoreConfiguration.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultCoreConfiguration.java @@ -18,10 +18,13 @@ import java.time.ZonedDateTime; import java.time.temporal.TemporalAccessor; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -29,21 +32,28 @@ import org.springframework.data.domain.AuditorAware; import org.springframework.web.client.RestTemplate; +import com.antheminc.oss.nimbus.channel.web.RemoteModelClientHttpRequestInterceptor; import com.antheminc.oss.nimbus.channel.web.WebActionController; import com.antheminc.oss.nimbus.channel.web.WebCommandBuilder; import com.antheminc.oss.nimbus.channel.web.WebCommandDispatcher; import com.antheminc.oss.nimbus.context.BeanResolverStrategy; import com.antheminc.oss.nimbus.domain.defn.ClassPropertyConverter; +import com.antheminc.oss.nimbus.domain.defn.Repo; import com.antheminc.oss.nimbus.domain.model.state.repo.DefaultModelRepositoryFactory; import com.antheminc.oss.nimbus.domain.model.state.repo.DefaultParamStateRepositoryDetached; import com.antheminc.oss.nimbus.domain.model.state.repo.DefaultParamStateRepositoryLocal; +import com.antheminc.oss.nimbus.domain.model.state.repo.ModelRepository; import com.antheminc.oss.nimbus.domain.model.state.repo.ModelRepositoryFactory; import com.antheminc.oss.nimbus.domain.model.state.repo.ParamStateRepository; import com.antheminc.oss.nimbus.domain.model.state.repo.ParamStateRepositoryGateway; import com.antheminc.oss.nimbus.domain.model.state.repo.SpringSecurityAuditorAware; import com.antheminc.oss.nimbus.domain.model.state.repo.db.ParamStateAtomicPersistenceEventListener; import com.antheminc.oss.nimbus.domain.model.state.repo.ws.DefaultWSModelRepository; +import com.antheminc.oss.nimbus.domain.model.state.repo.ws.ParamStateAtomicRemotePersistenceEventListener; +import com.antheminc.oss.nimbus.domain.model.state.repo.ws.RemoteWSModelRepository; import com.antheminc.oss.nimbus.domain.rules.DefaultRulesEngineFactoryProducer; +import com.antheminc.oss.nimbus.domain.rules.drools.DecisionTableConfigBuilder; +import com.antheminc.oss.nimbus.domain.rules.drools.DrlConfigBuilder; import com.antheminc.oss.nimbus.domain.rules.drools.DroolsRulesEngineFactory; import com.antheminc.oss.nimbus.support.pojo.JavaBeanHandler; import com.antheminc.oss.nimbus.support.pojo.JavaBeanHandlerReflection; @@ -54,16 +64,42 @@ */ @Configuration @ComponentScan(basePackageClasses = WebActionController.class) +@EnableCaching public class DefaultCoreConfiguration { @Bean public DefaultModelRepositoryFactory defaultModelRepositoryFactory(BeanResolverStrategy beanResolver){ - return new DefaultModelRepositoryFactory(beanResolver); + /*Add ModelRepository implementation beans to a lookup map*/ + Map repoBeanLookup = new HashMap<>(); + repoBeanLookup.put(Repo.Remote.rep_remote_ws.name(), beanResolver.get(ModelRepository.class, Repo.Remote.rep_remote_ws.name())); + repoBeanLookup.put(Repo.Database.rep_mongodb.name(), beanResolver.get(ModelRepository.class, Repo.Database.rep_mongodb.name())); + repoBeanLookup.put(Repo.Database.rep_ws.name(), beanResolver.get(ModelRepository.class, Repo.Database.rep_ws.name())); + + return new DefaultModelRepositoryFactory(beanResolver, repoBeanLookup); } @Bean(name="default.rep_ws") - public DefaultWSModelRepository defaultWSModelRepository(BeanResolverStrategy beanResolver){ - return new DefaultWSModelRepository(beanResolver); + public DefaultWSModelRepository defaultWSModelRepository(BeanResolverStrategy beanResolver, RestTemplateBuilder builder){ + RestTemplate restTemplate = beanResolver.get(RestTemplate.class, "rep_ws.restTemplate"); + return new DefaultWSModelRepository(beanResolver, restTemplate); + } + + @Bean(name="default.rep_remote_ws") + public RemoteWSModelRepository remoteWSModelRepository(BeanResolverStrategy beanResolver){ + RestTemplate restTemplate = beanResolver.get(RestTemplate.class, "rep_remote_ws.restTemplate"); + return new RemoteWSModelRepository(beanResolver, restTemplate); + } + + @Bean(name="default.rep_ws.restTemplate") + public RestTemplate wsRestTemplate(RestTemplateBuilder builder) { + RestTemplate restTemplate = builder.build(); + return restTemplate; + } + + @Bean(name="default.rep_remote_ws.restTemplate") + public RestTemplate remoteWSRestTemplate(RestTemplateBuilder builder) { + RestTemplate restTemplate = builder.additionalInterceptors(new RemoteModelClientHttpRequestInterceptor()).build(); + return restTemplate; } @Bean(name="default.paramStateAtomicPersistenceEventListener") @@ -71,10 +107,11 @@ public ParamStateAtomicPersistenceEventListener paramStateAtomicPersistenceEvent return new ParamStateAtomicPersistenceEventListener(repoFactory); } -// @Bean(name="default.paramStateBatchPersistenceEventListener") -// public ParamStateBulkPersistenceEventListener paramStateBatchPersistenceEventListener(ModelRepositoryFactory repoFactory){ -// return new ParamStateBulkPersistenceEventListener(repoFactory); -// } + @Bean(name="default.paramStateAtomicRemotePersistenceEventListener") + public ParamStateAtomicRemotePersistenceEventListener paramStateAtomicRemotePersistenceEventListener(ModelRepositoryFactory repoFactory){ + return new ParamStateAtomicRemotePersistenceEventListener(repoFactory); + } + @Bean(name="default.param.state.rep_local") public DefaultParamStateRepositoryLocal defaultParamStateRepositoryLocal(JavaBeanHandler javaBeanHandler){ @@ -95,8 +132,18 @@ public ParamStateRepositoryGateway paramStateRepositoryGateway(JavaBeanHandler j //rules drools @Bean(name="rules.factory.drools") - public DroolsRulesEngineFactory droolsRulesEngineFactory(){ - return new DroolsRulesEngineFactory(); + public DroolsRulesEngineFactory droolsRulesEngineFactory(BeanResolverStrategy beanResolver){ + return new DroolsRulesEngineFactory(beanResolver); + } + + @Bean(name="rules.factory.drools.drl") + public DrlConfigBuilder drlConfigBuilder(){ + return new DrlConfigBuilder(); + } + + @Bean(name="rules.factory.drools.dtable") + public DecisionTableConfigBuilder dtableConfigBuilder(){ + return new DecisionTableConfigBuilder(); } @Bean(name="default.rules.factory.producer") @@ -126,6 +173,7 @@ public AuditorAware auditorProvider(BeanResolverStrategy beanResolver) { return new SpringSecurityAuditorAware(beanResolver); } + @Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder.build(); diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultFrameworkExtensionsConfig.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultFrameworkExtensionsConfig.java index 04e3b7300..11ba71771 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultFrameworkExtensionsConfig.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultFrameworkExtensionsConfig.java @@ -21,7 +21,10 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import com.antheminc.oss.nimbus.channel.web.ResponseInterceptor; +import com.antheminc.oss.nimbus.channel.web.HttpRawResponseBodyHeaderInterceptor; import com.antheminc.oss.nimbus.context.BeanResolverStrategy; +import com.antheminc.oss.nimbus.domain.cmd.exec.MultiExecuteOutput; import com.antheminc.oss.nimbus.domain.defn.extension.ValidateConditional.ValidationScope; import com.antheminc.oss.nimbus.domain.model.config.extension.LabelStateEventHandler; import com.antheminc.oss.nimbus.domain.model.state.extension.AccessConditionalStateEventHandler; @@ -38,7 +41,7 @@ import com.antheminc.oss.nimbus.domain.model.state.extension.ParamContextStateEventHandler; import com.antheminc.oss.nimbus.domain.model.state.extension.ParamValuesOnLoadHandler; import com.antheminc.oss.nimbus.domain.model.state.extension.RuleStateEventHandler; -import com.antheminc.oss.nimbus.domain.model.state.extension.ScriptStateLoadNewHandler; +import com.antheminc.oss.nimbus.domain.model.state.extension.ScriptEventHandler; import com.antheminc.oss.nimbus.domain.model.state.extension.StaticCodeValueBasedCodeToLabelConverter; import com.antheminc.oss.nimbus.domain.model.state.extension.StyleConditionalStateEventHandler; import com.antheminc.oss.nimbus.domain.model.state.extension.ValidateConditionalStateEventHandler; @@ -145,8 +148,8 @@ public Map extensionValidateCondi } @Bean - public ScriptStateLoadNewHandler extensionScriptStateLoadNewHandler(BeanResolverStrategy beanResolver) { - return new ScriptStateLoadNewHandler(beanResolver); + public ScriptEventHandler extensionScriptStateLoadNewHandler(BeanResolverStrategy beanResolver) { + return new ScriptEventHandler(beanResolver); } @Bean @@ -169,4 +172,9 @@ public StaticCodeValueBasedCodeToLabelConverter staticCodeValueBasedCodeToLabelC return new StaticCodeValueBasedCodeToLabelConverter(beanResolver); } + @Bean(name="default.httpresponsebodyinterceptor._raw") + public ResponseInterceptor remoteModelResponseInterceptor(BeanResolverStrategy beanResolver) { + return new HttpRawResponseBodyHeaderInterceptor(); + } + } diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultMongoConfig.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultMongoConfig.java index fb4561d17..f02b6f3ff 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultMongoConfig.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultMongoConfig.java @@ -18,7 +18,6 @@ */ package com.antheminc.oss.nimbus.app.extension.config; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.config.EnableMongoAuditing; @@ -27,9 +26,7 @@ import com.antheminc.oss.nimbus.context.BeanResolverStrategy; import com.antheminc.oss.nimbus.domain.model.state.repo.IdSequenceRepository; -import com.antheminc.oss.nimbus.domain.model.state.repo.ModelRepository; import com.antheminc.oss.nimbus.domain.model.state.repo.MongoIdSequenceRepository; -import com.antheminc.oss.nimbus.domain.model.state.repo.db.mongo.DefaultMongoModelPersistenceHandler; import com.antheminc.oss.nimbus.domain.model.state.repo.db.mongo.DefaultMongoModelRepository; import com.antheminc.oss.nimbus.support.mongo.MongoConvertersBuilder; @@ -46,11 +43,6 @@ public MongoCustomConversions defaultMongoCustomConversions() { return new MongoConvertersBuilder().addDefaults().build(); } - @Bean(name="default.rep_mongodb_handler") - public DefaultMongoModelPersistenceHandler defaultMongoModelPersistenceHandler(@Qualifier("default.rep_mongodb") ModelRepository rep){ - return new DefaultMongoModelPersistenceHandler(rep); - } - @Bean(name="default.rep_mongodb") public DefaultMongoModelRepository defaultMongoModelRepository(MongoOperations mongoOps, IdSequenceRepository idSequenceRepo, BeanResolverStrategy beanResolver){ return new DefaultMongoModelRepository(mongoOps, idSequenceRepo, beanResolver); diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultProcessConfig.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultProcessConfig.java index 1802befa2..2b0dcb9e3 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultProcessConfig.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/app/extension/config/DefaultProcessConfig.java @@ -19,12 +19,15 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.core.MongoOperations; import com.antheminc.oss.nimbus.context.BeanResolverStrategy; import com.antheminc.oss.nimbus.domain.bpm.BPMGateway; +import com.antheminc.oss.nimbus.domain.bpm.ProcessRepository; import com.antheminc.oss.nimbus.domain.bpm.activiti.ActivitiBPMGateway; import com.antheminc.oss.nimbus.domain.bpm.activiti.ActivitiExpressionManager; import com.antheminc.oss.nimbus.domain.bpm.activiti.CommandExecutorTaskDelegate; +import com.antheminc.oss.nimbus.domain.bpm.activiti.DefaultMongoProcessRepository; import com.antheminc.oss.nimbus.domain.cmd.exec.FunctionHandler; import com.antheminc.oss.nimbus.domain.cmd.exec.internal.DefaultParamFunctionHandler; import com.antheminc.oss.nimbus.domain.cmd.exec.internal.nav.DefaultActionNewInitEntityFunctionHandler; @@ -61,6 +64,11 @@ public BPMGateway bpmGateway(BeanResolverStrategy beanResolver){ return new ActivitiBPMGateway(beanResolver,supportStatefulProcesses); } + @Bean + public ProcessRepository processRepository(MongoOperations mongoOps) { + return new DefaultMongoProcessRepository(mongoOps); + } + @Bean(name="default._new$execute?fn=_initEntity") public FunctionHandler defaultActionNewInitFunctionHandler(BeanResolverStrategy beanResolver){ return new DefaultActionNewInitEntityFunctionHandler<>(beanResolver); @@ -112,8 +120,8 @@ public CommandExecutorTaskDelegate commandExecutorTaskDelegate(BeanResolverStrat } @Bean(name="default._search$execute?fn=lookup") - public FunctionHandler lookupFunctionHandler(){ - return new DefaultSearchFunctionHandlerLookup<>(); + public FunctionHandler lookupFunctionHandler(BeanResolverStrategy beanResolver){ + return new DefaultSearchFunctionHandlerLookup<>(beanResolver); } @Bean(name="default._search$execute?fn=example") diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/channel/web/HttpRawResponseBodyHeaderInterceptor.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/channel/web/HttpRawResponseBodyHeaderInterceptor.java new file mode 100644 index 000000000..c2cc1c761 --- /dev/null +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/channel/web/HttpRawResponseBodyHeaderInterceptor.java @@ -0,0 +1,57 @@ +/** + * Copyright 2016-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.antheminc.oss.nimbus.channel.web; + +import org.apache.commons.collections.CollectionUtils; + +import com.antheminc.oss.nimbus.domain.cmd.exec.CommandExecution.MultiOutput; +import com.antheminc.oss.nimbus.domain.cmd.exec.CommandExecution.Output; +import com.antheminc.oss.nimbus.domain.cmd.exec.MultiExecuteOutput; +import com.antheminc.oss.nimbus.domain.model.state.EntityState.Param; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * @author Rakesh Patel + * + */ +@RequiredArgsConstructor @Getter +public class HttpRawResponseBodyHeaderInterceptor implements ResponseInterceptor { + + @Override + public boolean intercept(MultiExecuteOutput responseBody) { + MultiOutput output = responseBody.getSingleResult(); + + if(output != null) { + if(CollectionUtils.isEmpty(output.getOutputs())) + return false; + + if(output.getOutputs().size() > 1) // assumes the remote model call cannot have more than one outputs per request since it is meant for CRUD + return false; + + Object obj = output.getSingleResult(); + if(obj instanceof Param) { + Param param = (Param) obj; + Object entityState = param.getLeafState(); + Output o = (Output)output.getOutputs().get(0); + o.setValue(entityState); + } + } + return true; + } + +} diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/channel/web/RemoteModelClientHttpRequestInterceptor.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/channel/web/RemoteModelClientHttpRequestInterceptor.java new file mode 100644 index 000000000..b3caee2d5 --- /dev/null +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/channel/web/RemoteModelClientHttpRequestInterceptor.java @@ -0,0 +1,57 @@ +/** + * Copyright 2016-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.antheminc.oss.nimbus.channel.web; + +import java.io.IOException; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.support.HttpRequestWrapper; + +import com.antheminc.oss.nimbus.domain.defn.Constants; + +/** + * @author Rakesh Patel + * + */ +public class RemoteModelClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) + throws IOException { + + return execution.execute(new OverrideHttpRequestWrapper(request), body); + } + + + private class OverrideHttpRequestWrapper extends HttpRequestWrapper { + + public OverrideHttpRequestWrapper(HttpRequest request) { + super(request); + } + + @Override + public HttpHeaders getHeaders() { + HttpHeaders headers = super.getHeaders(); + headers.add(Constants.HTTP_RESPONSEBODY_INTERCEPTOR_HEADER.code, Constants.HTTP_RESPONSEBODY_INTERCEPTOR_HEADER_RAW.code); + return headers; + } + } + +} diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/channel/web/ResponseInterceptor.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/channel/web/ResponseInterceptor.java new file mode 100644 index 000000000..e15586117 --- /dev/null +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/channel/web/ResponseInterceptor.java @@ -0,0 +1,14 @@ +/** + * + */ +package com.antheminc.oss.nimbus.channel.web; + +/** + * @author Rakesh Patel + * + */ +public interface ResponseInterceptor { + + public boolean intercept(I responseBody); + +} diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/channel/web/WebActionControllerAdvice.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/channel/web/WebActionControllerAdvice.java index 4354a37da..985498b69 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/channel/web/WebActionControllerAdvice.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/channel/web/WebActionControllerAdvice.java @@ -40,6 +40,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import com.antheminc.oss.nimbus.FrameworkRuntimeException; +import com.antheminc.oss.nimbus.context.BeanResolverStrategy; import com.antheminc.oss.nimbus.domain.cmd.exec.CommandTransactionInterceptor; import com.antheminc.oss.nimbus.domain.cmd.exec.ExecuteError; import com.antheminc.oss.nimbus.domain.cmd.exec.ExecuteOutput; @@ -65,6 +66,8 @@ public class WebActionControllerAdvice implements ResponseBodyAdvice { private JustLogit logit = new JustLogit(this.getClass()); + private static final String RESPONSE_INTERCEPTOR_BEAN_PREFIX = "httpresponsebodyinterceptor."; + @Value("${application.error.metricLoggingEnabled:true}") private boolean metricLoggingEnabled; @@ -74,13 +77,16 @@ public class WebActionControllerAdvice implements ResponseBodyAdvice { @Value("${application.error.genericMsg:#{null}}") private String genericMsg; - @Autowired CommandTransactionInterceptor interceptor; + @Autowired CommandTransactionInterceptor defaultInterceptor; + + @Autowired BeanResolverStrategy beanResolver; @Override public boolean supports(MethodParameter returnType, Class> converterType) { return true; } + @SuppressWarnings("unchecked") @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { @@ -88,7 +94,20 @@ public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType logit.debug(()->"Processed response from "+WebActionController.class+": " + "\n"+ body); - MultiExecuteOutput multiOutput = interceptor.handleResponse(body); + MultiExecuteOutput multiOutput = defaultInterceptor.handleResponse(body); + + String responseBodyHeader = request.getHeaders().getFirst(Constants.HTTP_RESPONSEBODY_INTERCEPTOR_HEADER.code); + + if(StringUtils.isBlank(responseBodyHeader)) + return multiOutput; + + ResponseInterceptor interceptor = beanResolver.find(ResponseInterceptor.class, RESPONSE_INTERCEPTOR_BEAN_PREFIX+responseBodyHeader); + + if(interceptor == null) + return multiOutput; + + interceptor.intercept(multiOutput); + return multiOutput; } @@ -116,7 +135,7 @@ public MultiExecuteOutput exception(Throwable pEx){ execError.setMessage(message); logError(execError, pEx); resp.setExecuteException(execError); - return interceptor.handleResponse(resp); + return defaultInterceptor.handleResponse(resp); } private void logError(ExecuteError execError, Throwable pEx) { @@ -150,7 +169,7 @@ public MultiExecuteOutput exception(MethodArgumentNotValidException vEx){ resp.setValidationResult(new ValidationResult()); resp.getValidationResult().setErrors(errors); - return interceptor.handleResponse(resp); + return defaultInterceptor.handleResponse(resp); } private String constructMessage(ExecuteError err) { @@ -175,4 +194,5 @@ private ExecuteError constructExecError(Throwable pEx) { return execError; } + } diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/Event.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/Event.java index 782f20d51..b44667699 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/Event.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/Event.java @@ -23,6 +23,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; +import com.antheminc.oss.nimbus.domain.defn.event.EventType; + /** * Marker annotation for framework generated events * @@ -38,4 +40,7 @@ int DEFAULT_ORDER_NUMBER = Integer.MAX_VALUE; int order() default DEFAULT_ORDER_NUMBER; + + EventType[] eventType() default {}; + } diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/bpm/ProcessRepository.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/bpm/ProcessRepository.java new file mode 100644 index 000000000..f3319221d --- /dev/null +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/bpm/ProcessRepository.java @@ -0,0 +1,20 @@ +/** + * + */ +package com.antheminc.oss.nimbus.domain.bpm; + +import java.io.Serializable; + +/** + * @author Rakesh Patel + * + */ +public interface ProcessRepository { + + public T _save(T state, String alias); + + public T _update(String alias, Serializable id, String path, T state); + + public T _get(Serializable id, Class clazz, String alias); + +} diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/bpm/activiti/ActivitiBPMGateway.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/bpm/activiti/ActivitiBPMGateway.java index 654a389f0..44c7005b6 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/bpm/activiti/ActivitiBPMGateway.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/bpm/activiti/ActivitiBPMGateway.java @@ -25,7 +25,6 @@ import org.activiti.bpmn.model.UserTask; import org.activiti.engine.RuntimeService; import org.activiti.engine.TaskService; -import org.activiti.engine.delegate.DelegateExecution; import org.activiti.engine.impl.context.Context; import org.activiti.engine.impl.interceptor.CommandExecutor; import org.activiti.engine.impl.persistence.deploy.DeploymentManager; @@ -40,18 +39,19 @@ import com.antheminc.oss.nimbus.context.BeanResolverStrategy; import com.antheminc.oss.nimbus.domain.bpm.BPMGateway; import com.antheminc.oss.nimbus.domain.bpm.ProcessEngineContext; +import com.antheminc.oss.nimbus.domain.bpm.ProcessRepository; import com.antheminc.oss.nimbus.domain.cmd.Command; import com.antheminc.oss.nimbus.domain.cmd.CommandBuilder; -import com.antheminc.oss.nimbus.domain.cmd.CommandMessage; import com.antheminc.oss.nimbus.domain.cmd.CommandElement.Type; +import com.antheminc.oss.nimbus.domain.cmd.CommandMessage; +import com.antheminc.oss.nimbus.domain.cmd.CommandMessageConverter; +import com.antheminc.oss.nimbus.domain.cmd.exec.CommandExecution.MultiOutput; +import com.antheminc.oss.nimbus.domain.cmd.exec.CommandExecution.Output; import com.antheminc.oss.nimbus.domain.cmd.exec.CommandExecutorGateway; import com.antheminc.oss.nimbus.domain.cmd.exec.CommandPathVariableResolver; import com.antheminc.oss.nimbus.domain.cmd.exec.ProcessResponse; -import com.antheminc.oss.nimbus.domain.cmd.exec.CommandExecution.MultiOutput; -import com.antheminc.oss.nimbus.domain.cmd.exec.CommandExecution.Output; import com.antheminc.oss.nimbus.domain.config.builder.DomainConfigBuilder; import com.antheminc.oss.nimbus.domain.defn.Constants; -import com.antheminc.oss.nimbus.domain.defn.Repo; import com.antheminc.oss.nimbus.domain.model.config.ModelConfig; import com.antheminc.oss.nimbus.domain.model.state.EntityState.Param; import com.antheminc.oss.nimbus.domain.model.state.internal.ExecutionEntity; @@ -83,6 +83,7 @@ public class ActivitiBPMGateway implements BPMGateway { private DomainConfigBuilder domainConfigBuilder; private CommandExecutorGateway commandGateway; private CommandPathVariableResolver pathVariableResolver; + private ProcessRepository processRepo; public ActivitiBPMGateway (BeanResolverStrategy beanResolver,Boolean supportStatefulProcesses) { this.expressionEvaluator = beanResolver.find(ExpressionEvaluator.class); @@ -94,12 +95,17 @@ public ActivitiBPMGateway (BeanResolverStrategy beanResolver,Boolean supportStat this.domainConfigBuilder = beanResolver.find(DomainConfigBuilder.class); this.commandGateway = beanResolver.find(CommandExecutorGateway.class); this.pathVariableResolver = beanResolver.find(CommandPathVariableResolver.class); + this.processRepo = beanResolver.get(ProcessRepository.class); } @Override public ActivitiProcessFlow startBusinessProcess(Param param, String processId) { if(!getSupportStatefulProcesses()) return null; + + if(param.getRootDomain().getConfig().isRemote()) + return null; + ProcessResponse processResponse = startStatlessBusinessProcess(param, processId); ActivitiProcessFlow processFlow = new ActivitiProcessFlow(); processFlow.setProcessExecutionId(processResponse.getExecutionId()); @@ -130,6 +136,9 @@ public ProcessResponse startStatlessBusinessProcess(Param param, String proce @Override public Object continueBusinessProcessExecution(Param param, String processExecutionId) { + if(param.getRootDomain().getConfig().isRemote()) + return null; + ActivitiProcessFlow processFlow = (ActivitiProcessFlow)((ExecutionEntity)param.getRootExecution().getState()).getFlow(); List activeTasks = processFlow.getActiveTasks(); ProcessEngineContext context = new ProcessEngineContext(param); @@ -174,16 +183,20 @@ private void executeTask(Param param, String processExecutionId, String task) private void updateProcessState(Param param, String processExecutionId) { ActivitiProcessFlow processFlow = (ActivitiProcessFlow)((ExecutionEntity)param.getRootExecution().getState()).getFlow(); + List activeTasks = new ArrayList(); List activeTasksFromDB = getTaskService().createTaskQuery().processInstanceId(processExecutionId).list(); activeTasksFromDB.iterator().forEachRemaining( t -> activeTasks.add(t.getTaskDefinitionKey())); + processFlow.setActiveTasks(activeTasks); + ModelConfig modelConfig = domainConfigBuilder.getModel(ProcessFlow.class); - Repo repo = modelConfig.getRepo(); - String processStateAlias = StringUtils.isBlank(repo.alias()) ? modelConfig.getAlias() : repo.alias(); - String entityProcessAlias = param.getRootDomain().getConfig().getAlias() + "_" + processStateAlias; + + String processStateAlias = modelConfig.getRepoAlias(); + String entityProcessAlias = param.getRootDomain().getConfig().getRepoAlias() + "_" + processStateAlias; + Long entityRefId = param.getRootExecution().getRootCommand().getRefId(Type.DomainAlias); - repositoryFactory.get(repo)._update(entityProcessAlias, entityRefId, "/activeTasks", activeTasks); - processFlow.setActiveTasks(activeTasks); + + getProcessRepo()._update(entityProcessAlias, entityRefId, "/activeTasks", activeTasks); } private boolean canComplete(Param param, UserTask userTask) { diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/bpm/activiti/ActivitiUserTaskActivityBehavior.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/bpm/activiti/ActivitiUserTaskActivityBehavior.java index f3b3539d6..c2d46f8fb 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/bpm/activiti/ActivitiUserTaskActivityBehavior.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/bpm/activiti/ActivitiUserTaskActivityBehavior.java @@ -28,26 +28,30 @@ import com.antheminc.oss.nimbus.context.BeanResolverStrategy; import com.antheminc.oss.nimbus.domain.bpm.ProcessEngineContext; +import com.antheminc.oss.nimbus.domain.bpm.ProcessRepository; import com.antheminc.oss.nimbus.domain.cmd.Command; import com.antheminc.oss.nimbus.domain.cmd.CommandBuilder; import com.antheminc.oss.nimbus.domain.cmd.CommandElement.Type; import com.antheminc.oss.nimbus.domain.cmd.CommandMessage; +import com.antheminc.oss.nimbus.domain.cmd.CommandMessageConverter; import com.antheminc.oss.nimbus.domain.cmd.exec.CommandExecution.MultiOutput; import com.antheminc.oss.nimbus.domain.cmd.exec.CommandExecution.Output; import com.antheminc.oss.nimbus.domain.cmd.exec.CommandExecutorGateway; import com.antheminc.oss.nimbus.domain.cmd.exec.CommandPathVariableResolver; import com.antheminc.oss.nimbus.domain.config.builder.DomainConfigBuilder; import com.antheminc.oss.nimbus.domain.defn.Constants; -import com.antheminc.oss.nimbus.domain.defn.Repo; import com.antheminc.oss.nimbus.domain.model.config.ModelConfig; import com.antheminc.oss.nimbus.domain.model.state.internal.ExecutionEntity; import com.antheminc.oss.nimbus.domain.model.state.repo.ModelRepositoryFactory; import com.antheminc.oss.nimbus.entity.process.ProcessFlow; +import lombok.Getter; + /** * @author Rakesh Patel * */ +@Getter public class ActivitiUserTaskActivityBehavior extends UserTaskActivityBehavior { private static final long serialVersionUID = 1L; @@ -59,6 +63,7 @@ public class ActivitiUserTaskActivityBehavior extends UserTaskActivityBehavior { BeanResolverStrategy beanResolver; ModelRepositoryFactory repositoryFactory; DomainConfigBuilder domainConfigBuilder; + ProcessRepository processRepo; public ActivitiUserTaskActivityBehavior(UserTask userTask) { @@ -136,7 +141,7 @@ private void init(){ this.pathVariableResolver = beanResolver.find(CommandPathVariableResolver.class); this.repositoryFactory = beanResolver.find(ModelRepositoryFactory.class); this.domainConfigBuilder = beanResolver.find(DomainConfigBuilder.class); - + this.processRepo = beanResolver.get(ProcessRepository.class); } } @@ -173,12 +178,17 @@ private void removeActiveTask(DelegateExecution execution) { } protected void updateProcessState(ProcessEngineContext context, List activeTasks) { + ActivitiProcessFlow processFlow = (ActivitiProcessFlow)((ExecutionEntity)context.getParam().getRootExecution().getState()).getFlow(); + processFlow.setActiveTasks(activeTasks); + ModelConfig modelConfig = domainConfigBuilder.getModel(ProcessFlow.class); - Repo repo = modelConfig.getRepo(); - String processStateAlias = StringUtils.isBlank(repo.alias()) ? modelConfig.getAlias() : repo.alias(); - String entityProcessAlias = context.getParam().getRootDomain().getConfig().getAlias() + "_" + processStateAlias; + + String processStateAlias = modelConfig.getRepoAlias(); + String entityProcessAlias = context.getParam().getRootDomain().getConfig().getRepoAlias() + "_" + processStateAlias; + Long entityRefId = context.getParam().getRootExecution().getRootCommand().getRefId(Type.DomainAlias); - repositoryFactory.get(repo)._update(entityProcessAlias, entityRefId, "/activeTasks", activeTasks); + + getProcessRepo()._update(entityProcessAlias, entityRefId, "/activeTasks", activeTasks); } } \ No newline at end of file diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/bpm/activiti/DefaultMongoProcessRepository.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/bpm/activiti/DefaultMongoProcessRepository.java new file mode 100644 index 000000000..b1b2ecb31 --- /dev/null +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/bpm/activiti/DefaultMongoProcessRepository.java @@ -0,0 +1,73 @@ +/** + * + */ +package com.antheminc.oss.nimbus.domain.bpm.activiti; + +import java.io.Serializable; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; + +import com.antheminc.oss.nimbus.domain.bpm.ProcessRepository; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * @author Rakesh Patel + * + */ +@RequiredArgsConstructor @Getter +public class DefaultMongoProcessRepository implements ProcessRepository { + + private final MongoOperations mongoOps; + + + @Override + public T _save(T state, String alias) { + getMongoOps().save(state, alias); + return state; + } + + + @Override + public T _update(String alias, Serializable id, String path, T state) { + path = resolvePath(path); + + Query query = new Query(Criteria.where("_id").is(id)); + Update update = new Update(); + + if (StringUtils.isBlank(path) || StringUtils.equalsIgnoreCase(path, "/c")) { + getMongoOps().save(state, alias); + } else { + if (StringUtils.equals(path, "/id") || StringUtils.equals(path, "id")) { + return state; + } + path = StringUtils.substringAfter(path, "/"); + path = path.replaceAll("/", "\\."); + if (state == null) + update.unset(path); + else + update.set(path, state); + getMongoOps().upsert(query, update, alias); + } + return state; + } + + @Override + public T _get(Serializable id, Class referredClass, String alias) { + T state = getMongoOps().findById(id, referredClass, alias); + return state; + } + + + private String resolvePath(String path) { + String p = StringUtils.replace(path, "/c/", "/"); + p = StringUtils.replace(p, "/v/", "/"); + return p; + } + +} diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/Command.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/Command.java index be78bab63..74bc15e40 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/Command.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/Command.java @@ -22,8 +22,10 @@ import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.stream.Stream; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import com.antheminc.oss.nimbus.FrameworkRuntimeException; @@ -372,18 +374,40 @@ public String toUri() { } /* behavior(s) */ - sb.append("?").append(Constants.MARKER_URI_BEHAVIOR.code).append("="); // ?b= + sb.append(Constants.REQUEST_PARAMETER_MARKER.code).append(Constants.MARKER_URI_BEHAVIOR.code).append(Constants.PARAM_ASSIGNMENT_MARKER.code); // ?b= sb.append(getBehaviors().get(0).name()); // $execute (or other behavior) getBehaviors().stream().sequential().skip(1).forEach(b->{ sb.append(Constants.SEPARATOR_AND.code).append(b.name()); }); - /* TODO: other request params */ + addRequestParamsToUri(sb); return sb.toString(); } + public String toRemoteUri(Type endWhenType, Action withAction, Behavior withBehavior) { + String baseUri = buildUri(endWhenType); + StringBuilder sb = new StringBuilder(baseUri); + + /* action */ + sb.append(Constants.SEPARATOR_URI.code).append(withAction.name()); + + /* behavior=$execute */ + sb.append(Constants.REQUEST_PARAMETER_MARKER.code).append(Constants.MARKER_URI_BEHAVIOR.code).append(Constants.PARAM_ASSIGNMENT_MARKER.code); // ?b= + sb.append(withBehavior); // $execute (or other behavior) + + return sb.toString(); + } + + private void addRequestParamsToUri(StringBuilder sb) { + if(MapUtils.isEmpty(getRequestParams())) + return; + + getRequestParams().entrySet().stream() + .filter(e -> !StringUtils.equals(e.getKey()+Constants.PARAM_ASSIGNMENT_MARKER.code, Constants.MARKER_URI_BEHAVIOR.code+Constants.PARAM_ASSIGNMENT_MARKER.code)) + .forEach(e -> Stream.of(e.getValue()).forEach(v -> sb.append(Constants.REQUEST_PARAMETER_DELIMITER.code).append(e.getKey()).append(Constants.PARAM_ASSIGNMENT_MARKER.code).append(v))); + } public String[] getParameterValue(String requestParameter){ if(requestParams != null && requestParams.containsKey(requestParameter)){ @@ -413,4 +437,5 @@ public String getRawPayload() { public boolean containsFunction() { return requestParams != null && requestParams.containsKey(Constants.KEY_FUNCTION.code); } + } diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/CommandBuilder.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/CommandBuilder.java index af698bb8c..7ae88c2f0 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/CommandBuilder.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/CommandBuilder.java @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; +import java.util.List; import java.util.Map; import org.apache.commons.collections.CollectionUtils; @@ -58,6 +59,26 @@ public static CommandBuilder withUri(String absoluteUri) { return cb; } + public static CommandBuilder withCommand(Command cmd) { + CommandBuilder cb = new CommandBuilder(cmd); + cb.handleUriAndParamsIfAny(cmd.getAbsoluteUri()); + return cb; + } + + /** + * Builds a CommandBuilder by appending a given platform path to a uri untill a given end CommandElementType + * @param rootCmd + * @param endWhenType + * @param appendPath + * @return + */ + public static CommandBuilder withPlatformRelativePath(Command rootCmd, Type endWhenType, String appendPath) { + String hostUri = rootCmd.buildUri(endWhenType); + StringBuilder absoluteUri = new StringBuilder(hostUri); + absoluteUri.append(appendPath); + return withUri(absoluteUri.toString()); + } + public static CommandBuilder withDomainRelativePath(Command rootCmd, String domainRelPath) { String rootDomainAlias = rootCmd.getRootDomainAlias(); String nestedDomainPath = StringUtils.removeStart(Constants.SEPARATOR_URI.code + rootDomainAlias, domainRelPath); @@ -233,4 +254,13 @@ public CommandBuilder addParams(Map rParams) { return this; } + public CommandBuilder setAction(Action action) { + cmd.setAction(action); + return withCommand(cmd); + } + + public CommandBuilder setBehaviors(List behaviors) { + cmd.setBehaviors(behaviors); + return withCommand(cmd); + } } diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/CommandElementLinked.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/CommandElementLinked.java index c3b08ba09..d56155737 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/CommandElementLinked.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/CommandElementLinked.java @@ -54,19 +54,6 @@ public CommandElementLinked(CommandElementLinked source) { setNext(nextCloned); } - public CommandElementLinked cloneUpto(Type type, StringBuilder absoluteUri) { - if(getType().equals(type)) - return null; - CommandElementLinked cloned = new CommandElementLinked(getSeqNum(), getType(), getAlias(), getRefId()); - absoluteUri.append(getUri()); - CommandElementLinked nextCloned = Optional.ofNullable(getNext()).map(n -> n.cloneUpto(type, absoluteUri)).orElse(null); - - if(nextCloned != null) - cloned.setNext(nextCloned); - - return cloned; - } - public CommandElementLinked next() { return next; } diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/AbstractCommandExecutor.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/AbstractCommandExecutor.java index 1ff6edbff..b8f0a60e2 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/AbstractCommandExecutor.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/AbstractCommandExecutor.java @@ -19,22 +19,19 @@ import org.apache.commons.lang3.StringUtils; -import com.antheminc.oss.nimbus.FrameworkRuntimeException; import com.antheminc.oss.nimbus.context.BeanResolverStrategy; -import com.antheminc.oss.nimbus.domain.cmd.Command; import com.antheminc.oss.nimbus.domain.cmd.CommandElement.Type; -import com.antheminc.oss.nimbus.domain.cmd.CommandElementLinked; import com.antheminc.oss.nimbus.domain.cmd.CommandMessageConverter; import com.antheminc.oss.nimbus.domain.cmd.exec.CommandExecution.Input; import com.antheminc.oss.nimbus.domain.cmd.exec.CommandExecution.Output; import com.antheminc.oss.nimbus.domain.config.builder.DomainConfigBuilder; import com.antheminc.oss.nimbus.domain.defn.Repo; -import com.antheminc.oss.nimbus.domain.model.config.EntityConfig; import com.antheminc.oss.nimbus.domain.model.config.ModelConfig; import com.antheminc.oss.nimbus.domain.model.config.ParamConfig; import com.antheminc.oss.nimbus.domain.model.state.EntityState.ValueAccessor; import com.antheminc.oss.nimbus.domain.model.state.builder.QuadModelBuilder; import com.antheminc.oss.nimbus.domain.model.state.internal.ExecutionEntity; +import com.antheminc.oss.nimbus.domain.model.state.repo.ModelRepository; import com.antheminc.oss.nimbus.domain.model.state.repo.ModelRepositoryFactory; import com.antheminc.oss.nimbus.support.pojo.JavaBeanHandler; import com.antheminc.oss.nimbus.support.pojo.JavaBeanHandlerUtils; @@ -81,19 +78,6 @@ final public Output execute(Input input) { protected ModelConfig getRootDomainConfig(ExecutionContext eCtx) { return getDomainConfigBuilder().getRootDomainOrThrowEx(eCtx.getCommandMessage().getCommand().getRootDomainAlias()); } - - protected EntityConfig findConfigByCommand(ExecutionContext eCtx) { - Command cmd = eCtx.getCommandMessage().getCommand(); - - ModelConfig domainConfig = getRootDomainConfig(eCtx); - if(cmd.isRootDomainOnly()) - return domainConfig; - - String path = cmd.buildAlias(cmd.getElementSafely(Type.DomainAlias).next()); - - ParamConfig nestedParamConfig = domainConfig.findParamByPath(path); - return nestedParamConfig; - } protected String resolveEntityAliasByRepo(ModelConfig mConfig) { String alias = mConfig.getRepo() != null && StringUtils.isNotBlank(mConfig.getRepo().alias()) @@ -102,14 +86,14 @@ protected String resolveEntityAliasByRepo(ModelConfig mConfig) { return alias; } - protected T instantiateEntity(ExecutionContext eCtx, ModelConfig mConfig) { - /* - if(eCtx.getCommandMessage().hasPayload()) - return converter.convert(mConfig.getReferredClass(), eCtx.getCommandMessage()); - */ - Repo repo = mConfig.getRepo(); + protected T getOrInstantiateEntity(ExecutionContext eCtx, ModelConfig mConfig) { + Long refId = eCtx.getCommandMessage().getCommand().getRefId(Type.DomainAlias); + ModelRepository mRepo = getRepositoryFactory().get(mConfig); - return (repo!=null && repo.value()!=Repo.Database.rep_none) ? getRepositoryFactory().get(repo)._new(mConfig) : javaBeanHandler.instantiate(mConfig.getReferredClass()); + return mRepo != null + ? refId != null ? mRepo._get(eCtx.getCommandMessage().getCommand(), mConfig) + : mRepo._new(eCtx.getCommandMessage().getCommand(), mConfig) + : javaBeanHandler.instantiate(mConfig.getReferredClass()); } public interface RepoDBCallback { @@ -155,20 +139,6 @@ public Object whenMappedRootDomainHasRepo(ModelConfig mapsToConfig) { .map(Long::valueOf) .orElse(null); } - - protected ModelConfig getRootConfigByRepoDatabase(ModelConfig rootDomainConfig) { - return determineByRepoDatabase(rootDomainConfig, new RepoDBCallback>() { - @Override - public ModelConfig whenRootDomainHasRepo() { - return rootDomainConfig; - } - - @Override - public ModelConfig whenMappedRootDomainHasRepo(ModelConfig mapsToConfig) { - return mapsToConfig; - } - }); - } protected Object getRefId(ModelConfig parentModelConfig, ParamConfig pConfig, Object entity) { ValueAccessor va = JavaBeanHandlerUtils.constructValueAccessor(parentModelConfig.getReferredClass(), pConfig.getCode()); diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/ExecuteError.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/ExecuteError.java index 4b8000ca2..69d0a36ea 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/ExecuteError.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/ExecuteError.java @@ -49,16 +49,6 @@ public ExecuteError() { } - public ExecuteError(String uniqueId, Class exClass, String message) { - Assert.notNull(uniqueId, "uniqueId must not be null"); - setUniqueId(uniqueId); - - Assert.notNull(exClass, "Exception class must not be null"); - setCode(exClass.getName() + CODE_SUFFIX); - - setMessage(message); - } - public ExecuteError(Class exClass, String message) { Assert.notNull(exClass, "Exception class must not be null"); setCode(exClass.getName() + CODE_SUFFIX); diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/ExecuteOutput.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/ExecuteOutput.java index b5f6e14c7..135663a59 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/ExecuteOutput.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/ExecuteOutput.java @@ -78,4 +78,12 @@ public static class HolderValue { } private List> outputs; } + + @Getter @Setter @SuppressWarnings("serial") @ToString(callSuper=true) + public static class GenericListExecute extends ExecuteOutput>>>> { + + public List extractSingleValue() { + return getResult().get(0).getResult().getOutputs().get(0).getValue(); + } + } } diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/ValidationException.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/ValidationException.java deleted file mode 100644 index 6f872db60..000000000 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/ValidationException.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright 2016-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.antheminc.oss.nimbus.domain.cmd.exec; - -import java.util.Set; - -import javax.validation.ConstraintViolation; - -import com.antheminc.oss.nimbus.FrameworkRuntimeException; - -import lombok.Getter; - -/** - * @author Soham Chakravarti - * - */ -public class ValidationException extends FrameworkRuntimeException { - - private static final long serialVersionUID = 1L; - - - @Getter private final ValidationResult validationResult; - - - public ValidationException(Set> violations) { - this.validationResult = new ValidationResult(violations); - } - - public ValidationException(Set> violations, String message) { - super(message); - this.validationResult = new ValidationResult(violations); - } - - public ValidationException(ValidationResult result) { - this.validationResult = result; - } - - public ValidationException(ValidationResult result, String message) { - super(message); - this.validationResult = result; - } - -} diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/ValidationResult.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/ValidationResult.java index e4b360a73..f23250f6c 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/ValidationResult.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/ValidationResult.java @@ -38,8 +38,8 @@ public class ValidationResult { private List errors; @JsonIgnore - public final transient CollectionsTemplate, ValidationError> template = new CollectionsTemplate<>( - () -> getErrors(), (e) -> setErrors(e), () -> new ArrayList<>()); + public final CollectionsTemplate, ValidationError> template = new CollectionsTemplate<>( + this::getErrors, this::setErrors, ArrayList::new); public ValidationResult(){ } @@ -62,7 +62,9 @@ public void addValidationErrors(Set> violations) { * @param violations */ private void add(Set> violations) { - if(CollectionUtils.isEmpty(violations)) return; + if(CollectionUtils.isEmpty(violations)) { + return; + } for(ConstraintViolation c : violations) { ValidationError.Model e = new ValidationError.Model(); diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/CommandExecutionContext.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/CommandExecutionContext.java deleted file mode 100644 index 7474b0f9a..000000000 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/CommandExecutionContext.java +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright 2016-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.antheminc.oss.nimbus.domain.cmd.exec.internal; - -import java.util.LinkedList; -import java.util.List; - -import com.antheminc.oss.nimbus.domain.cmd.Command; -import com.antheminc.oss.nimbus.domain.model.state.ParamEvent; -import com.antheminc.oss.nimbus.support.pojo.CollectionsTemplate; - -import lombok.Getter; -import lombok.Setter; - -/** - * TODO: Work In Progress - * @author Soham Chakravarti - * - */ -@Getter -public class CommandExecutionContext { - - private final Command command; - private final List events = new LinkedList<>(); - - protected CommandExecutionContext currentContext; - - @Setter - private List subContexts; - - private final CollectionsTemplate, CommandExecutionContext> templateSubContexts = CollectionsTemplate.linked(this::getSubContexts, this::setSubContexts); - - public static class RootCommandExecutionContext extends CommandExecutionContext { - public RootCommandExecutionContext(Command command) { - super(command); - } - } - - public CommandExecutionContext(Command command) { - this.command = command; - } - - protected void setCurrentContext(CommandExecutionContext currentContext) { - this.currentContext = currentContext; - } - - public static CommandExecutionContext root(Command rootCmd) { - return new RootCommandExecutionContext(rootCmd); - } - - - - public CommandExecutionContext addNext(Command childCmd) { - CommandExecutionContext childCtx = new CommandExecutionContext(childCmd); - templateSubContexts.add(childCtx); - - return this; - } -} diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultActionExecutorDelete.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultActionExecutorDelete.java index e796fb0c4..409d267d6 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultActionExecutorDelete.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultActionExecutorDelete.java @@ -15,16 +15,16 @@ */ package com.antheminc.oss.nimbus.domain.cmd.exec.internal; +import java.util.Optional; + import javax.annotation.PostConstruct; import com.antheminc.oss.nimbus.context.BeanResolverStrategy; -import com.antheminc.oss.nimbus.domain.cmd.CommandElement.Type; import com.antheminc.oss.nimbus.domain.cmd.exec.AbstractCommandExecutor; import com.antheminc.oss.nimbus.domain.cmd.exec.CommandExecution.Input; import com.antheminc.oss.nimbus.domain.cmd.exec.CommandExecution.Output; import com.antheminc.oss.nimbus.domain.cmd.exec.ExecutionContext; import com.antheminc.oss.nimbus.domain.cmd.exec.ExecutionContextLoader; -import com.antheminc.oss.nimbus.domain.defn.Repo; import com.antheminc.oss.nimbus.domain.model.config.ModelConfig; import com.antheminc.oss.nimbus.domain.model.state.EntityState.ListElemParam; import com.antheminc.oss.nimbus.domain.model.state.EntityState.ListParam; @@ -50,7 +50,6 @@ public void initDependencies() { } - @Override protected Output executeInternal(Input input) { ExecutionContext eCtx = input.getContext(); @@ -59,53 +58,44 @@ protected Output executeInternal(Input input) { // handle domain root only if(eCtx.getCommandMessage().getCommand().isRootDomainOnly()) { - handleRootDelete(eCtx); + handleRootDelete(p); loader.unload(eCtx); } else if(p.isCollection()) - handleCollection(eCtx, p.findIfCollection()); + handleCollection(p.findIfCollection()); else if(p.isCollectionElem()) - handleCollectionElem(eCtx, p.findIfCollectionElem()); + handleCollectionElem(p.findIfCollectionElem()); else - handleParam(eCtx, p); + handleParam(p); return Output.instantiate(input, eCtx, Boolean.TRUE); } - protected void handleRootDelete(ExecutionContext eCtx) { - Long refId = eCtx.getCommandMessage().getCommand().getRefId(Type.DomainAlias); - - ModelConfig rootDomainConfig = getRootDomainConfig(eCtx); - Repo repo = rootDomainConfig.getRepo(); + protected void handleRootDelete(Param p) { + ModelConfig rootDomainConfig = p.getRootDomain().getConfig(); - if(Repo.Database.exists(repo)) { - getRepositoryFactory().get(repo) - ._delete(refId, rootDomainConfig.getReferredClass(), rootDomainConfig.getAlias()); - } + Optional.ofNullable(getRepositoryFactory().get(rootDomainConfig)).ifPresent(mRepo -> mRepo._delete(p)); + if(rootDomainConfig.isRemote()) + return; + if(rootDomainConfig.isMapped()) { - ModelConfig mapsToConfig = rootDomainConfig.findIfMapped().getMapsToConfig(); - Repo mapsToRepo = mapsToConfig.getRepo(); - - if(Repo.Database.exists(mapsToRepo)) { - getRepositoryFactory().get(mapsToRepo) - ._delete(refId, mapsToConfig.getReferredClass(), mapsToConfig.getAlias()); - } - + Param mapsToParam = p.getRootDomain().findIfMapped().getMapsTo().getAssociatedParam(); + Optional.ofNullable(getRepositoryFactory().get(mapsToParam.getRootDomain().getConfig())).ifPresent(mapsToRepo -> mapsToRepo._delete(mapsToParam)); } } - protected void handleCollection(ExecutionContext eCtx, ListParam p) { + protected void handleCollection(ListParam p) { p.clear(); } - protected void handleCollectionElem(ExecutionContext eCtx, ListElemParam p) { + protected void handleCollectionElem(ListElemParam p) { p.remove(); } - protected void handleParam(ExecutionContext eCtx, Param p) { + protected void handleParam(Param p) { p.setState(null); } } \ No newline at end of file diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultActionExecutorGet.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultActionExecutorGet.java index 22ca45348..00e172ac7 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultActionExecutorGet.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultActionExecutorGet.java @@ -21,6 +21,7 @@ import com.antheminc.oss.nimbus.InvalidConfigException; import com.antheminc.oss.nimbus.context.BeanResolverStrategy; +import com.antheminc.oss.nimbus.domain.bpm.ProcessRepository; import com.antheminc.oss.nimbus.domain.cmd.Action; import com.antheminc.oss.nimbus.domain.cmd.Command; import com.antheminc.oss.nimbus.domain.cmd.CommandBuilder; @@ -55,11 +56,14 @@ public class DefaultActionExecutorGet extends AbstractCommandExecutor { private DomainConfigBuilder domainConfigBuilder; + private ProcessRepository processRepo; + public DefaultActionExecutorGet(BeanResolverStrategy beanResolver) { super(beanResolver); this.commandGateway = getBeanResolver().find(CommandExecutorGateway.class); this.domainConfigBuilder = getBeanResolver().find(DomainConfigBuilder.class); + this.processRepo = getBeanResolver().find(ProcessRepository.class); } @@ -88,21 +92,8 @@ protected ExecutionContext handleGetDomainRoot(ExecutionContext eCtx) { } protected QuadModel createNewQuad(ModelConfig rootDomainConfig, ExecutionContext eCtx) { - final Long refId = eCtx.getCommandMessage().getCommand().getRefId(Type.DomainAlias); - - final Object entity; - final Repo repo = rootDomainConfig.getRepo(); - final String resolvedRepAlias = resolveEntityAliasByRepo(rootDomainConfig); - - // db - entity - if(Repo.Database.exists(repo) && refId != null) { // root (view or core) is persistent - entity = getRepositoryFactory().get(rootDomainConfig.getRepo()) - ._get(refId, rootDomainConfig.getReferredClass(), resolvedRepAlias, eCtx.getCommandMessage().getCommand().getAbsoluteUri()); - - } else { - entity = instantiateEntity(eCtx, rootDomainConfig); - } - + final Object entity = getOrInstantiateEntity(eCtx, rootDomainConfig); + if(rootDomainConfig.isMapped()) return handleMapped(rootDomainConfig, eCtx, entity, Action._get); @@ -135,7 +126,7 @@ protected ExecutionContext handleGetDomainRoot(ExecutionContext eCtx) { Param coreParam = Optional.ofNullable(getCommandGateway().execute(mapsToCmd, null)) .map(mOut->(Param)mOut.getSingleResult()) - .orElseThrow(()->new InvalidStateException("Expeceted first response from command gateway to return mapsTo core parm, but not found for mapsToCmd: "+mapsToCmd)); + .orElseThrow(()->new InvalidStateException("Expected first response from command gateway to return mapsTo core parm, but not found for mapsToCmd: "+mapsToCmd)); QuadModel q = getQuadModelBuilder().build(eCtx.getCommandMessage().getCommand(), mapped, coreParam); q.getRoot().initState(); @@ -143,24 +134,29 @@ protected ExecutionContext handleGetDomainRoot(ExecutionContext eCtx) { } protected ProcessFlow loadProcessState(ModelConfig rootDomainConfig, ExecutionContext eCtx) { + if(rootDomainConfig.isRemote()) + return null; + String domainLifeCycle = rootDomainConfig.getDomainLifecycle(); if(StringUtils.trimToNull(domainLifeCycle)==null) return null; - ModelConfig modelConfig = getDomainConfigBuilder().getModel(ProcessFlow.class); - Repo repo = modelConfig.getRepo(); + ModelConfig processModelConfig = getDomainConfigBuilder().getModel(ProcessFlow.class); + Repo repo = processModelConfig.getRepo(); if(!Repo.Database.exists(repo)) throw new InvalidConfigException(ProcessFlow.class.getSimpleName()+" must have @Repo configured for db persistence, but found none."); - String processStateAlias = StringUtils.isBlank(repo.alias()) ? modelConfig.getAlias() : repo.alias(); - - final String resolvedEntityAlias = resolveEntityAliasByRepo(rootDomainConfig); + String processStateAlias = processModelConfig.getRepoAlias(); + String resolvedEntityAlias = rootDomainConfig.getRepoAlias(); String entityProcessAlias = resolvedEntityAlias + "_" + processStateAlias; final Long entityRefId = eCtx.getCommandMessage().getCommand().getRefId(Type.DomainAlias); - ProcessFlow processEntityState = getRepositoryFactory().get(repo)._get(entityRefId, ProcessFlow.class, entityProcessAlias); + + ProcessFlow processEntityState = getProcessRepo()._get(entityRefId, ProcessFlow.class, entityProcessAlias); + return processEntityState; + } } \ No newline at end of file diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultActionExecutorNew.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultActionExecutorNew.java index d69b627af..0327da654 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultActionExecutorNew.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultActionExecutorNew.java @@ -22,6 +22,7 @@ import com.antheminc.oss.nimbus.InvalidConfigException; import com.antheminc.oss.nimbus.context.BeanResolverStrategy; import com.antheminc.oss.nimbus.domain.bpm.BPMGateway; +import com.antheminc.oss.nimbus.domain.bpm.ProcessRepository; import com.antheminc.oss.nimbus.domain.cmd.Action; import com.antheminc.oss.nimbus.domain.cmd.Command; import com.antheminc.oss.nimbus.domain.cmd.CommandBuilder; @@ -60,12 +61,15 @@ public class DefaultActionExecutorNew extends AbstractCommandExecutor> private DomainConfigBuilder domainConfigBuilder; + private ProcessRepository processRepo; // TODO this dependency is to support process state persistence, temporary, need to re-design + public DefaultActionExecutorNew(BeanResolverStrategy beanResolver) { super(beanResolver); this.bpmGateway = beanResolver.get(BPMGateway.class); this.commandGateway = getBeanResolver().find(CommandExecutorGateway.class); this.domainConfigBuilder = getBeanResolver().find(DomainConfigBuilder.class); + this.processRepo = getBeanResolver().find(ProcessRepository.class); } /** @@ -117,6 +121,7 @@ protected ExecutionContext handleNewDomainRoot(ExecutionContext eCtx) { // hook up BPM Param rootDomainParam = getRootDomainParam(eCtx); + ProcessFlow processEntityState = startBusinessProcess(rootDomainParam); if(processEntityState==null) @@ -135,7 +140,7 @@ protected ExecutionContext handleNewDomainRoot(ExecutionContext eCtx) { private QuadModel createNewQuad(ModelConfig rootDomainConfig, ExecutionContext eCtx) { // create new entity instance for core & view - Object entity = instantiateEntity(eCtx, rootDomainConfig); + Object entity = getOrInstantiateEntity(eCtx, rootDomainConfig); // unmapped if(!rootDomainConfig.isMapped()) @@ -188,17 +193,17 @@ private ProcessFlow startBusinessProcess(Param rootDomainParam){ } protected void saveProcessState(String resolvedEntityAlias, ProcessFlow processEntityState) { - ModelConfig modelConfig = getDomainConfigBuilder().getModel(ProcessFlow.class); - Repo repo = modelConfig.getRepo(); + ModelConfig processModelConfig = getDomainConfigBuilder().getModel(ProcessFlow.class); + Repo repo = processModelConfig.getRepo(); if(!Repo.Database.exists(repo)) throw new InvalidConfigException(ProcessFlow.class.getSimpleName()+" must have @Repo configured for db persistence, but found none."); - String processStateAlias = StringUtils.isBlank(repo.alias()) ? modelConfig.getAlias() : repo.alias(); + String processStateAlias = processModelConfig.getRepoAlias(); String entityProcessAlias = resolvedEntityAlias + "_" + processStateAlias; - getRepositoryFactory().get(repo)._save(entityProcessAlias, processEntityState); + getProcessRepo()._save(processEntityState, entityProcessAlias); } } \ No newline at end of file diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultActionExecutorUpdate.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultActionExecutorUpdate.java index efc5fdc3b..2ac7de92c 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultActionExecutorUpdate.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultActionExecutorUpdate.java @@ -128,10 +128,10 @@ protected Output executeInternal(Input input) { Param p = findParamByCommandOrThrowEx(eCtx); String json = eCtx.getCommandMessage().getRawPayload(); - if (p.isLeaf()) { - handleLeaf(p, json); - } else if (p.isCollection()) { + if (p.isCollection()) { handleCollection(p.findIfCollection(), json); + } else if (p.isLeaf()) { + handleLeaf(p, json); } else { // remaining scenarios apply to nested params or collection elements handleParam(p, json); diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultCommandPathVariableResolver.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultCommandPathVariableResolver.java index e6df24b39..3fd04b3c8 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultCommandPathVariableResolver.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultCommandPathVariableResolver.java @@ -15,8 +15,12 @@ */ package com.antheminc.oss.nimbus.domain.cmd.exec.internal; +import java.util.Arrays; +import java.util.LinkedList; import java.util.Map; import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; @@ -25,8 +29,15 @@ import com.antheminc.oss.nimbus.InvalidConfigException; import com.antheminc.oss.nimbus.context.BeanResolverStrategy; +import com.antheminc.oss.nimbus.domain.cmd.Action; +import com.antheminc.oss.nimbus.domain.cmd.Behavior; +import com.antheminc.oss.nimbus.domain.cmd.Command; +import com.antheminc.oss.nimbus.domain.cmd.CommandBuilder; import com.antheminc.oss.nimbus.domain.cmd.CommandElement.Type; +import com.antheminc.oss.nimbus.domain.cmd.CommandMessage; import com.antheminc.oss.nimbus.domain.cmd.CommandMessageConverter; +import com.antheminc.oss.nimbus.domain.cmd.exec.CommandExecution.MultiOutput; +import com.antheminc.oss.nimbus.domain.cmd.exec.CommandExecutorGateway; import com.antheminc.oss.nimbus.domain.cmd.exec.CommandPathVariableResolver; import com.antheminc.oss.nimbus.domain.cmd.exec.ParamPathExpressionParser; import com.antheminc.oss.nimbus.domain.defn.Constants; @@ -49,18 +60,22 @@ public class DefaultCommandPathVariableResolver implements CommandPathVariableRe protected final JustLogit logit = new JustLogit(DefaultCommandPathVariableResolver.class); - private static final String STRING_NULL = "null"; + private static final String NULL_STRING = "null"; + private static final String NULL_STRING_REGEX = "\\s*\"null\"\\s*"; + private static final Pattern NULL_STRING_PATTERN = Pattern.compile(NULL_STRING_REGEX); private final CommandMessageConverter converter; private final PropertyResolver propertyResolver; private final SessionProvider sessionProvider; private final Environment environment; + private final CommandExecutorGateway executorGateway; public DefaultCommandPathVariableResolver(BeanResolverStrategy beanResolver, PropertyResolver propertyResolver) { this.converter = beanResolver.get(CommandMessageConverter.class); this.propertyResolver = propertyResolver; this.sessionProvider = beanResolver.get(SessionProvider.class); this.environment = beanResolver.get(Environment.class); + this.executorGateway = beanResolver.find(CommandExecutorGateway.class); } @@ -96,6 +111,8 @@ protected String resolveInternal(Param param, String urlToResolve) { out = StringUtils.replace(out, key, val, 1); } + Matcher m = NULL_STRING_PATTERN.matcher(out); + out = m.replaceAll(NULL_STRING); // replaces all json="null" (including leading/trailing spaces) to json=null return out; } @@ -120,6 +137,9 @@ protected String map(Param param, String pathToResolve) { if(StringUtils.startsWithIgnoreCase(pathToResolve, Constants.MARKER_ELEM_ID.code)) return mapColElem(param, pathToResolve); + if(StringUtils.startsWithIgnoreCase(pathToResolve, Constants.SEGMENT_PLATFORM_MARKER.code)) + return String.valueOf(mapCrossDomain(param, pathToResolve)); + return mapQuad(param, pathToResolve); } @@ -143,19 +163,24 @@ protected String mapEnvironment(Param param, String pathToResolve) { protected String mapQuad(Param param, String pathToResolve) { if(StringUtils.startsWith(pathToResolve, "json(")) { String paramPath = StringUtils.substringBetween(pathToResolve, "json(", ")"); - Param p = param.findParamByPath(paramPath) != null? param.findParamByPath(paramPath): param.getParentModel().findParamByPath(paramPath); - if(p == null) { - logit.error(() -> new StringBuffer().append(" Param (using paramPath) ").append(paramPath).append(" not found from param reference: ").append(param).toString()); - return STRING_NULL; + Object state; + if (StringUtils.startsWithIgnoreCase(paramPath, Constants.SEGMENT_PLATFORM_MARKER.code)) { + state = mapCrossDomain(param, paramPath); + } else { + Param p = param.findParamByPath(paramPath) != null? param.findParamByPath(paramPath): param.getParentModel().findParamByPath(paramPath); + if(p == null) { + logit.error(() -> new StringBuffer().append(" Param (using paramPath) ").append(paramPath).append(" not found from param reference: ").append(param).toString()); + return NULL_STRING; + } + state = p.getLeafState(); } - Object state = p.getLeafState(); String json = getConverter().toJson(state); return String.valueOf(json); } else { Param p = param.findParamByPath(pathToResolve) != null? param.findParamByPath(pathToResolve): param.getParentModel().findParamByPath(pathToResolve); if(p == null) { logit.error(() -> new StringBuffer().append(" Param (using paramPath) ").append(pathToResolve).append(" not found from param reference: ").append(param).toString()); - return STRING_NULL; + return NULL_STRING; } return String.valueOf(p.getState()); } @@ -173,4 +198,30 @@ protected String mapColElem(Param commandParam, String pathToResolve) { // throw ex ..or.. blank?? return ""; } + + /** + * + * @param commandParam + * @param pathToResolve + * @return + * @since 1.1.11 + */ + protected Object mapCrossDomain(Param commandParam, String pathToResolve) { + Command rootCmd = commandParam.getRootExecution().getRootCommand(); + CommandBuilder cmdBuilder = CommandBuilder.withPlatformRelativePath(rootCmd, Type.AppAlias, pathToResolve); + cmdBuilder.setAction(Action._get); + cmdBuilder.setBehaviors(new LinkedList<>(Arrays.asList(Behavior.$state))); + Command cmd = cmdBuilder.getCommand(); + CommandMessage cmdMsg = new CommandMessage(cmd, null); + MultiOutput output = executorGateway.execute(cmdMsg); + Object response = output.getSingleResult(); + + if (response == null) { + logit.error(() -> new StringBuffer().append(" Param (using paramPath) [").append(pathToResolve).append("] not found from param: ").append(commandParam).toString()); + return null; + } + + logit.debug(() -> new StringBuffer().append(" Param (using paramPath) [").append(pathToResolve).append("] has been resolved to ").append(response).toString()); + return response; + } } diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultExecutionContextLoader.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultExecutionContextLoader.java index e03882cc4..902879dfe 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultExecutionContextLoader.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/DefaultExecutionContextLoader.java @@ -120,7 +120,9 @@ private ExecutionContext loadEntity(ExecutionContext eCtx, CommandExecutor ex return eCtx; } - protected boolean sessionPutIfApplicable(ModelConfig rootDomainConfig, ExecutionContext eCtx) { + protected boolean sessionPutIfApplicable(ModelConfig rootDomainConfig, ExecutionContext eCtx) { + if (rootDomainConfig.isRemote()) + return false; Repo repo = rootDomainConfig.getRepo(); if(repo==null) return false; diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/ParamCodeValueProvider.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/ParamCodeValueProvider.java index 5fb0fd0fa..630dd6f9b 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/ParamCodeValueProvider.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/ParamCodeValueProvider.java @@ -28,6 +28,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import com.antheminc.oss.nimbus.FrameworkRuntimeException; +import com.antheminc.oss.nimbus.InvalidConfigException; import com.antheminc.oss.nimbus.domain.cmd.CommandElement.Type; import com.antheminc.oss.nimbus.domain.cmd.CommandMessage; import com.antheminc.oss.nimbus.domain.cmd.exec.CommandExecution.Input; @@ -69,7 +70,6 @@ public ParamCodeValueProvider(DefaultActionExecutorSearch searchExecutor) { * 1.2 DB * 2. Model as code values (in below order) * 2.1 config server, if not found - * 2.2 DB * */ @Override @@ -109,7 +109,7 @@ private List getStaticCodeValue(Input input) { } @SuppressWarnings("unchecked") - private R getModelAsCodeValue(Input input) { + private R getModelAsCodeValue(Input input) { CommandMessage cmdMsg = input.getContext().getCommandMessage(); // 2.1 config server lookup @@ -118,66 +118,6 @@ private R getModelAsCodeValue(Input input) { return (R)values.get(domainAlias); } - // 2.2 DB lookup - String[] keyValuePayload = cmdMsg.getRawPayload().split(KEY_VALUE_SEPERATOR); - cmdMsg.setRawPayload(null); // Clearing rawpayload as the payload for this command message is specific for key/value lookup and not supported by search executor - List> modelList = (List>)searchExecutor.execute(input); - - if(CollectionUtils.isNotEmpty(modelList)) { - String nestedDomainAlias = StringUtils.stripStart(cmdMsg.getCommand().getAbsoluteDomainAlias(), "/"); - - String[] nestedDomainModels = nestedDomainAlias.split("/"); - Object currentObject = modelList; - PropertyDescriptor currentPd = null; - if(nestedDomainModels.length > 1) { - for(int i=1; i> coll = null; - if(currentObject instanceof Collection) { - coll = (Collection>) currentObject; - } - else{ - coll = new ArrayList<>(); - coll.add((AbstractEntity)currentObject); - } - if (CollectionUtils.isEmpty(coll)) { - List paramValues = new ArrayList<>(); - return (R)paramValues; - } - Class codeValueClass = coll.iterator().next().getClass(); - - - PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(codeValueClass, DEFAULT_KEY_ATTRIBUTE); - PropertyDescriptor pd1 = BeanUtils.getPropertyDescriptor(codeValueClass, keyValuePayload.length > 0?keyValuePayload[0]:DEFAULT_KEY_ATTRIBUTE); - PropertyDescriptor pd2 = BeanUtils.getPropertyDescriptor(codeValueClass, keyValuePayload.length > 1?keyValuePayload[1]:keyValuePayload[0]); - - try { - List paramValues = new ArrayList<>(); - for(Object model: coll) { - paramValues.add(new ParamValue(pd.getReadMethod().invoke(model), (String)pd1.getReadMethod().invoke(model), (String)pd2.getReadMethod().invoke(model))); - } - return (R)paramValues; - } - catch(Exception ex) { - throw new FrameworkRuntimeException("Failed to execute read on property: "+pd, ex); - } - } - return null; + throw new InvalidConfigException("Unsupported Operation."); } - } diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/nav/DefaultActionNewInitEntityFunctionHandler.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/nav/DefaultActionNewInitEntityFunctionHandler.java index f03c8b92a..c6dff0303 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/nav/DefaultActionNewInitEntityFunctionHandler.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/nav/DefaultActionNewInitEntityFunctionHandler.java @@ -79,7 +79,7 @@ protected Object resolveTargetState(CommandMessage cmdMessage, int index, Param< json = cmdMessage.getRawPayload(); } - if(json == null || StringUtils.equalsIgnoreCase(json, "\"null\"")) + if(json == null) return null; return resolveAsJson(cmdMessage.getCommand(), targetParam.getConfig(), json); diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/search/DefaultSearchFunctionHandler.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/search/DefaultSearchFunctionHandler.java index 95591d7f8..cf4335aba 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/search/DefaultSearchFunctionHandler.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/search/DefaultSearchFunctionHandler.java @@ -47,17 +47,16 @@ @EnableLoggingInterceptor public abstract class DefaultSearchFunctionHandler extends AbstractFunctionHandler { + public static final String PROJECTIONS_SEPARATOR = ","; + public static final String KEY_VALUE_SEPARATOR = ":"; @Override public R execute(ExecutionContext executionContext, Param actionParameter) { ModelConfig mConfig = getRootDomainConfig(executionContext); - Class criteriaClass = mConfig.getReferredClass(); - String alias = findRepoAlias(mConfig); + ModelRepository rep = getRepFactory().get(mConfig); - ModelRepository rep = getRepFactory().get(mConfig.getRepo()); - - return (R)rep._search(criteriaClass, alias, () -> this.createSearchCriteria(executionContext, mConfig, actionParameter)); + return (R)rep._search(actionParameter, () -> this.createSearchCriteria(executionContext, mConfig, actionParameter)); } protected abstract SearchCriteria createSearchCriteria(ExecutionContext executionContext, ModelConfig mConfig, Param cmdParam); @@ -79,23 +78,23 @@ protected Pageable buildPageCriteria(Command cmd) { if(StringUtils.isNotBlank(pageSize) && StringUtils.isNotBlank(page)) { if(sortBy != null && sortBy.length > 0) { List sortByList = Stream.of(sortBy) - .map(s -> s.split(",")) + .map(s -> s.split(PROJECTIONS_SEPARATOR)) .filter(s -> s.length == 2) .map(s -> new Order(Direction.fromString(s[1]), s[0])) .collect(Collectors.toList()); - Sort sort = new Sort(sortByList); + Sort sort = Sort.by(sortByList); - return new PageRequest(Integer.valueOf(page), Integer.valueOf(pageSize), sort); + return PageRequest.of(Integer.valueOf(page), Integer.valueOf(pageSize), sort); } else{ - return new PageRequest(Integer.valueOf(page), Integer.valueOf(pageSize)); + return PageRequest.of(Integer.valueOf(page), Integer.valueOf(pageSize)); } } return null; } - protected ProjectCriteria buildProjectCritera(Command cmd) { + protected ProjectCriteria buildProjectCriteria(Command cmd) { if(cmd.getRequestParams().get(Constants.SEARCH_REQ_PROJECT_ALIAS_MARKER.code) != null) { ProjectCriteria projectCriteria = new ProjectCriteria(); projectCriteria.setAlias(cmd.getFirstParameterValue(Constants.SEARCH_REQ_PROJECT_ALIAS_MARKER.code)); @@ -105,13 +104,13 @@ protected ProjectCriteria buildProjectCritera(Command cmd) { if(cmd.getRequestParams().get(Constants.SEARCH_REQ_PROJECT_MAPPING_MARKER.code) != null) { ProjectCriteria projectCriteria = new ProjectCriteria(); String projectMapping = cmd.getFirstParameterValue(Constants.SEARCH_REQ_PROJECT_MAPPING_MARKER.code); - String[] keyValues = StringUtils.split(projectMapping,","); + String[] keyValues = StringUtils.split(projectMapping, PROJECTIONS_SEPARATOR); Stream.of(keyValues).forEach((kvString) -> { if(MapUtils.isEmpty(projectCriteria.getMapsTo())){ projectCriteria.setMapsTo(new HashMap()); } - String[] kv = StringUtils.split(kvString,":"); + String[] kv = StringUtils.split(kvString, KEY_VALUE_SEPARATOR); projectCriteria.getMapsTo().put(kv[0], kv[1]); }); return projectCriteria; diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/search/DefaultSearchFunctionHandlerExample.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/search/DefaultSearchFunctionHandlerExample.java index 3d9330719..725ead33e 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/search/DefaultSearchFunctionHandlerExample.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/search/DefaultSearchFunctionHandlerExample.java @@ -44,7 +44,7 @@ protected ExampleSearchCriteria createSearchCriteria(ExecutionContext executionC exampleSearchCriteria.setWhere(criteria); exampleSearchCriteria.setAggregateCriteria(cmd.getFirstParameterValue(Constants.SEARCH_REQ_AGGREGATE_MARKER.code)); - exampleSearchCriteria.setProjectCriteria(buildProjectCritera(cmd)); + exampleSearchCriteria.setProjectCriteria(buildProjectCriteria(cmd)); exampleSearchCriteria.setPageRequest(buildPageCriteria(cmd)); exampleSearchCriteria.setCmd(executionContext.getCommandMessage().getCommand()); diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/search/DefaultSearchFunctionHandlerLookup.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/search/DefaultSearchFunctionHandlerLookup.java index 505c4e049..f57af1465 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/search/DefaultSearchFunctionHandlerLookup.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/search/DefaultSearchFunctionHandlerLookup.java @@ -15,15 +15,19 @@ */ package com.antheminc.oss.nimbus.domain.cmd.exec.internal.search; -import java.beans.PropertyDescriptor; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cache.annotation.Cacheable; import com.antheminc.oss.nimbus.FrameworkRuntimeException; +import com.antheminc.oss.nimbus.InvalidConfigException; +import com.antheminc.oss.nimbus.context.BeanResolverStrategy; import com.antheminc.oss.nimbus.domain.cmd.Command; import com.antheminc.oss.nimbus.domain.cmd.CommandElement.Type; import com.antheminc.oss.nimbus.domain.cmd.exec.ExecutionContext; @@ -35,6 +39,9 @@ import com.antheminc.oss.nimbus.domain.model.state.repo.db.SearchCriteria.ProjectCriteria; import com.antheminc.oss.nimbus.entity.StaticCodeValue; import com.antheminc.oss.nimbus.support.EnableLoggingInterceptor; +import com.antheminc.oss.nimbus.support.JustLogit; +import com.antheminc.oss.nimbus.support.expr.ExpressionEvaluator; +import com.antheminc.oss.nimbus.support.pojo.CollectionsTemplate; /** * @author Rakesh Patel @@ -44,7 +51,28 @@ @EnableLoggingInterceptor public class DefaultSearchFunctionHandlerLookup extends DefaultSearchFunctionHandler { + private JustLogit logit = new JustLogit(this.getClass()); + + protected static final String PROPERTY_DELIMITER = "."; + protected static final String TO_REPLACE = "//"+PROPERTY_DELIMITER; + protected static final String REPLACE_WITH = "//?"+PROPERTY_DELIMITER; + + private ExpressionEvaluator expressionEvaluator; + + @Value(value="${search.lookup.inMemory.sortThreshold:500}") + private int inMemorySortThreshold; + + @Value(value="${search.lookup.inMemory.exceptionIfOverSortThreshold:false}") + private boolean exceptionIfOverSortThreshold; + + public DefaultSearchFunctionHandlerLookup(BeanResolverStrategy beanResolver) { + this.expressionEvaluator = beanResolver.find(ExpressionEvaluator.class); + } + @Override + @Cacheable(value="staticcodevalues", + key = "#executionContext.getCommandMessage().getCommand().getFirstParameterValue(\"where\")", + condition= "T(org.apache.commons.lang3.StringUtils).equalsIgnoreCase(#executionContext.getCommandMessage().getCommand().getElementSafely(T(com.antheminc.oss.nimbus.domain.cmd.CommandElement.Type).DomainAlias).getAlias(), \"staticCodeValue\")") public R execute(ExecutionContext executionContext, Param actionParameter) { ModelConfig mConfig = getRootDomainConfig(executionContext); @@ -53,10 +81,9 @@ public R execute(ExecutionContext executionContext, Param actionParameter) { Command cmd = executionContext.getCommandMessage().getCommand(); if(StringUtils.equalsIgnoreCase(cmd.getElementSafely(Type.DomainAlias).getAlias(), "staticCodeValue")) { - return getStaticParamValues((List)searchResult, cmd); + return getStaticParamValues((List)searchResult, mConfig, cmd); } - LookupSearchCriteria lookupSearchCriteria = createSearchCriteria(executionContext, mConfig, actionParameter); - return getDynamicParamValues(lookupSearchCriteria, mConfig.getReferredClass(), searchResult); + return getDynamicParamValues(mConfig, searchResult, cmd); } @@ -71,42 +98,99 @@ protected LookupSearchCriteria createSearchCriteria(ExecutionContext executionCo String where = executionContext.getCommandMessage().getCommand().getFirstParameterValue(Constants.SEARCH_REQ_WHERE_MARKER.code); lookupSearchCriteria.setWhere(where); - ProjectCriteria projectCriteria = buildProjectCritera(cmd); - lookupSearchCriteria.setProjectCriteria(projectCriteria); + lookupSearchCriteria.setOrderby(cmd.getFirstParameterValue(Constants.SEARCH_REQ_ORDERBY_MARKER.code)); + lookupSearchCriteria.setFetch(cmd.getFirstParameterValue(Constants.SEARCH_REQ_FETCH_MARKER.code)); + lookupSearchCriteria.setAggregateCriteria(cmd.getFirstParameterValue(Constants.SEARCH_REQ_AGGREGATE_MARKER.code)); + lookupSearchCriteria.setProjectCriteria(buildProjectCriteria(cmd)); lookupSearchCriteria.setCmd(executionContext.getCommandMessage().getCommand()); return lookupSearchCriteria; } - private R getStaticParamValues(List searchResult, Command cmd) { + private R getStaticParamValues(List searchResult, ModelConfig mConfig, Command cmd) { if(CollectionUtils.isEmpty(searchResult)) return null; if(CollectionUtils.size(searchResult) > 1) - throw new IllegalStateException("StaticCodeValue search for a command "+cmd+" returned more than one records for paramCode"); + throw new FrameworkRuntimeException("StaticCodeValue search for a command "+cmd+" returned more than one records, it must return only one record for the paramCode provided in the where clause"); + + List paramValues = searchResult.get(0).getParamValues(); - return (R) searchResult.get(0).getParamValues(); + return sortIfApplicable(paramValues, mConfig, cmd); + } - - private R getDynamicParamValues(LookupSearchCriteria lookupSearchCriteria, Class criteriaClass, List searchResult) { - List list = new ArrayList(lookupSearchCriteria.getProjectCriteria().getMapsTo().values()); + + private R getDynamicParamValues(ModelConfig mConfig, List searchResult, Command cmd) { + ProjectCriteria projectCriteria = buildProjectCriteria(cmd); + if(projectCriteria == null) + throw new InvalidConfigException("DynamicParamValues lookup needs projection.mapTo to create the param values for command: "+cmd+". found none."); + List list = new ArrayList(projectCriteria.getMapsTo().values()); + if(list.size() > 2) - throw new IllegalStateException("ParamValues lookup failed due to more than 2 fields provided to create the param values. the criteria class is "+criteriaClass); - - PropertyDescriptor codePd = BeanUtils.getPropertyDescriptor(criteriaClass, list.get(0)); - PropertyDescriptor labelPd = BeanUtils.getPropertyDescriptor(criteriaClass, list.get(1)); + throw new InvalidConfigException("ParamValues lookup failed due to more than 2 fields provided in projection to create the param values for command: "+cmd); + String cd = list.get(0).replaceAll(TO_REPLACE, REPLACE_WITH); + String lb = list.get(1).replaceAll(TO_REPLACE, REPLACE_WITH); try { List paramValues = new ArrayList<>(); for(Object model: searchResult) { - paramValues.add(new ParamValue(codePd.getReadMethod().invoke(model).toString(), (String)labelPd.getReadMethod().invoke(model))); + Object code = this.expressionEvaluator.getValue(cd, model); + Object label = this.expressionEvaluator.getValue(lb, model); + if(code!= null && label !=null) { + paramValues.add(new ParamValue(code.toString(), label.toString())); + } } - return (R)paramValues; + return sortIfApplicable(paramValues, mConfig, cmd); } catch(Exception ex) { - throw new FrameworkRuntimeException("Failed to execute read on property: "+codePd+" and "+labelPd, ex); + throw new FrameworkRuntimeException("Failed to parse property - code: "+list.get(0)+" and label: "+list.get(1)+" for command: "+cmd, ex); } } + /** + * In memory sorting of the param values + */ + private R sortIfApplicable(List paramValues, ModelConfig mConfig, Command cmd) { + String orderBy = cmd.getFirstParameterValue(Constants.SEARCH_REQ_ORDERBY_MARKER.code); + + if(StringUtils.isBlank(orderBy) || StringUtils.startsWith(orderBy, findRepoAlias(mConfig)+".")) + return (R) paramValues; + + int index = StringUtils.lastIndexOf(orderBy, PROPERTY_DELIMITER); + + if(index == -1) + throw new IllegalStateException("Invalid orderby clause for StaticCodeValue search for a command "+cmd+" . It must follow the pattern \"property.direction\" e.g. code.asc() or name.firstName.desc()"); + + if(inMemorySortThreshold <= 0 || paramValues.size() > inMemorySortThreshold) { + if(exceptionIfOverSortThreshold) { + throw new FrameworkRuntimeException("Size of paramValues "+paramValues.size()+" is greater than configured limit for in memory sort: "+inMemorySortThreshold+", for command: "+cmd+". " + + "Please configure the limit as needed keeping in mind that above the threshold limit, you may encounter memory leak."); + } + else { + logit.error(() -> "Size of paramValues "+paramValues.size()+" is greater than configured limit for in memory sort: "+inMemorySortThreshold+", for command: "+cmd+" so returning the list without sorting. " + + "Please configure the limit as needed keeping in mind that above the threshold limit, you may encounter memory leak." ); + return (R) paramValues; + } + } + + String property = StringUtils.substringBeforeLast(orderBy, PROPERTY_DELIMITER); + String direction = StringUtils.substringAfterLast(orderBy, PROPERTY_DELIMITER); + + try { + if(StringUtils.equalsAnyIgnoreCase(direction, Constants.SEARCH_REQ_ORDERBY_DESC_MARKER.code)) + CollectionsTemplate.sortSelfReverse(paramValues, Comparator.comparing(pv -> expressionEvaluator.getValue(property, pv, String.class))); + else if(StringUtils.equalsAnyIgnoreCase(direction, Constants.SEARCH_REQ_ORDERBY_ASC_MARKER.code)) + CollectionsTemplate.sortSelf(paramValues, Comparator.comparing(pv -> expressionEvaluator.getValue(property, pv, String.class))); + else + throw new FrameworkRuntimeException("Valid sort direction(s) are "+Constants.SEARCH_REQ_ORDERBY_DESC_MARKER.code+" OR "+Constants.SEARCH_REQ_ORDERBY_ASC_MARKER.code+" but found: "+direction+" , in command: "+cmd); + } + catch(Exception e) { + throw new FrameworkRuntimeException("Could not sort the param values with provided orderBy clause: "+orderBy+" , in command: "+cmd, e); + } + + return (R) paramValues; + } + + } diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/search/DefaultSearchFunctionHandlerQuery.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/search/DefaultSearchFunctionHandlerQuery.java index f77fde54c..33b8c6c17 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/search/DefaultSearchFunctionHandlerQuery.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/cmd/exec/internal/search/DefaultSearchFunctionHandlerQuery.java @@ -50,7 +50,7 @@ protected QuerySearchCriteria createSearchCriteria(ExecutionContext executionCon querySearchCriteria.setFetch(cmd.getFirstParameterValue(Constants.SEARCH_REQ_FETCH_MARKER.code)); querySearchCriteria.setAggregateCriteria(cmd.getFirstParameterValue(Constants.SEARCH_REQ_AGGREGATE_MARKER.code)); - querySearchCriteria.setProjectCriteria(buildProjectCritera(cmd)); + querySearchCriteria.setProjectCriteria(buildProjectCriteria(cmd)); querySearchCriteria.setPageRequest(buildPageCriteria(cmd)); querySearchCriteria.setCmd(executionContext.getCommandMessage().getCommand()); diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/config/builder/DefaultAnnotationConfigHandler.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/config/builder/DefaultAnnotationConfigHandler.java index ca9c37746..7df058ec8 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/config/builder/DefaultAnnotationConfigHandler.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/config/builder/DefaultAnnotationConfigHandler.java @@ -38,7 +38,6 @@ import com.antheminc.oss.nimbus.InvalidConfigException; import com.antheminc.oss.nimbus.domain.RepeatContainer; import com.antheminc.oss.nimbus.domain.model.config.AnnotationConfig; -import com.antheminc.oss.nimbus.support.EnableLoggingInterceptor; import lombok.AccessLevel; import lombok.Getter; @@ -49,7 +48,6 @@ * */ @RequiredArgsConstructor - @Getter(value=AccessLevel.PROTECTED) public class DefaultAnnotationConfigHandler implements AnnotationConfigHandler { diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/config/builder/DomainConfigBuilder.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/config/builder/DomainConfigBuilder.java index 48c097921..b0a95b2f0 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/config/builder/DomainConfigBuilder.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/config/builder/DomainConfigBuilder.java @@ -26,6 +26,8 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.core.type.filter.RegexPatternTypeFilter; +import org.springframework.core.type.filter.TypeFilter; import com.antheminc.oss.nimbus.InvalidConfigException; import com.antheminc.oss.nimbus.domain.defn.Domain; @@ -35,6 +37,8 @@ import com.antheminc.oss.nimbus.support.JustLogit; import com.antheminc.oss.nimbus.support.pojo.ClassLoadUtils; +import java.util.regex.Pattern; + import lombok.AccessLevel; import lombok.Getter; @@ -50,17 +54,22 @@ public class DomainConfigBuilder { private final EntityConfigBuilder configBuilder; private final List basePackages; + private final List packagesToexclude; + + ClassPathScanningCandidateComponentProvider scanner; protected final JustLogit logit = new JustLogit(DomainConfigBuilder.class); - public DomainConfigBuilder(EntityConfigBuilder configBuilder, List basePackages) { + public DomainConfigBuilder(EntityConfigBuilder configBuilder, List basePackages, List packagesToexclude) { this.configBuilder = configBuilder; this.basePackages = Optional.ofNullable(basePackages) .orElseThrow(()->new InvalidConfigException("base packages must be configured for scanning domain models.")); this.cacheDomainRootModel = new HashMap<>(); this.configVisitor = new EntityConfigVisitor(); + this.packagesToexclude=packagesToexclude; + this.scanner = new ClassPathScanningCandidateComponentProvider(false); } public ModelConfig getRootDomainOrThrowEx(String rootAlias) { @@ -85,6 +94,12 @@ public ModelConfig getModel(Class mClass) { public void load() { logit.trace(()->"Start-> Load model config..."); logit.trace(()->"Configured basePackages: "+getBasePackages()); + logit.trace(()->"Excluded basePackages: "+getPackagesToexclude()); + + if (packagesToexclude != null) + packagesToexclude.forEach(pkg -> scanner.addExcludeFilter(new RegexPatternTypeFilter(Pattern.compile(pkg)))); + + scanner.addIncludeFilter(new AnnotationTypeFilter(Domain.class)); List rootBasePackages = EntityConfigVisitor.determineRootPackages(getBasePackages()); @@ -94,10 +109,7 @@ public void load() { } public void handlePackage(String basePackage) { - - ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); - scanner.addIncludeFilter(new AnnotationTypeFilter(Domain.class)); - + for (BeanDefinition bd : scanner.findCandidateComponents(basePackage)) { String classNm = bd.getBeanClassName(); diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/config/builder/EventAnnotationConfigHandler.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/config/builder/EventAnnotationConfigHandler.java new file mode 100644 index 000000000..15afcd94f --- /dev/null +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/config/builder/EventAnnotationConfigHandler.java @@ -0,0 +1,148 @@ +/** + * Copyright 2016-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + */ +package com.antheminc.oss.nimbus.domain.config.builder; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationUtils; + +import com.antheminc.oss.nimbus.InvalidConfigException; +import com.antheminc.oss.nimbus.domain.Event; +import com.antheminc.oss.nimbus.domain.RepeatContainer; +import com.antheminc.oss.nimbus.domain.defn.event.EventType; +import com.antheminc.oss.nimbus.domain.model.config.AnnotationConfig; + +import lombok.AccessLevel; +import lombok.Getter; + +/** + * @author Rakesh Patel + * + */ +@Getter(value=AccessLevel.PROTECTED) +public class EventAnnotationConfigHandler implements AnnotationConfigHandler { + + @Override + public AnnotationConfig handleSingle(AnnotatedElement aElem, Class metaAnnotationType) { + throw new UnsupportedOperationException("Operation not supported by EventAnnotationConfigHandler - only supports repeatable meta event annotations"); + } + + @Override + public List handle(AnnotatedElement aElem, Class metaAnnotationType) { + throw new UnsupportedOperationException("Operation not supported by EventAnnotationConfigHandler - only supports repeatable meta event annotations"); + } + + @Override + public List handleRepeatable(AnnotatedElement aElem, Class repeatableMetaAnnotationType) { + final List annotations = new ArrayList<>(); + + final Annotation arr[] = aElem.getAnnotations(); + + for(final Annotation currDeclaredAnnotation : arr) { + final Set metaTypesOnCurrDeclaredAnnotation = AnnotatedElementUtils.getMetaAnnotationTypes(aElem, currDeclaredAnnotation.annotationType()); + + // handle repeatable container + if(metaTypesOnCurrDeclaredAnnotation!=null && metaTypesOnCurrDeclaredAnnotation.contains(RepeatContainer.class.getName())) { + + // get repeat container meta annotation and use declared repeatable annotation + RepeatContainer repeatContainerMetaAnnotation = AnnotationUtils.getAnnotation(currDeclaredAnnotation, RepeatContainer.class); + Class repeatableAnnotationDeclared = repeatContainerMetaAnnotation.value(); + + // check that the declared annotation has passed in meta annotation type + + boolean hasRepeatable = AnnotatedElementUtils.hasAnnotation(repeatableAnnotationDeclared, Event.class); + if(hasRepeatable) { + Object value = AnnotationUtils.getValue(currDeclaredAnnotation); + if(value==null || !value.getClass().isArray()) + throw new InvalidConfigException("Repeatable container annotation is expected to follow convention: '{RepeableAnnotationType}[] value();' but not found in: "+currDeclaredAnnotation); + + Annotation[] annArr = (Annotation[])value; + Stream.of(annArr).forEach(ann -> { + addIfcontainsAnnotationAsEventType(ann, repeatableMetaAnnotationType, annotations); + }); + } + } + + // handle non-repeating meta annotation + if(metaTypesOnCurrDeclaredAnnotation != null && metaTypesOnCurrDeclaredAnnotation.contains(Event.class.getName())) { + addIfcontainsAnnotationAsEventType(currDeclaredAnnotation, repeatableMetaAnnotationType, annotations); + } + } + + return annotations; + } + + private void addIfcontainsAnnotationAsEventType(Annotation ann, Class repeatableMetaAnnotationType, final List annotations) { + Object eventTypes = AnnotationUtils.getValue(ann, "eventType"); + + if(eventTypes != null) { + if(!(eventTypes instanceof EventType[])) + throw new InvalidConfigException("eventType attribute must contain elements of type "+EventType.class.getName()+" for annotation "+ann); + + if(containsRepeatableMetaAnnotation((EventType[])eventTypes, repeatableMetaAnnotationType)) + annotations.add(ann); + } + else { + findEventMetaAnnotationRecursively(ann, ann.annotationType().getDeclaredAnnotations(), repeatableMetaAnnotationType, annotations); + } + } + + /** + * do a meta (recursive) search on the annotated element for the {@link Event } type + * + * @param annotatedElement the annotated element + * @param foundAnnotations the already found annotations + */ + private void findEventMetaAnnotationRecursively(final Annotation annotation, final Annotation[] declaredAnnotations, Class repeatableMetaAnnotationType, List annotations) { + for(Annotation a : declaredAnnotations) { + boolean b = a.annotationType() == Event.class || AnnotatedElementUtils.hasAnnotation(a.annotationType(), Event.class); + if(b) { + Object eventTypes = AnnotationUtils.getValue(a, "eventType"); + if(eventTypes != null) { + if(!(eventTypes instanceof EventType[])) + throw new InvalidConfigException("eventType attribute must contain elements of type "+EventType.class.getName()+" for annotation "+a); + + if(containsRepeatableMetaAnnotation((EventType[])eventTypes, repeatableMetaAnnotationType)) + annotations.add(annotation); + break; + } + else { + findEventMetaAnnotationRecursively(annotation, a.annotationType().getDeclaredAnnotations(), repeatableMetaAnnotationType, annotations); + } + } + } + } + + private boolean containsRepeatableMetaAnnotation(EventType[] eventTypes, Class repeatableMetaAnnotationType) { + return Optional.ofNullable(eventTypes) + .map(Stream::of) + .orElse(Stream.empty()) + .anyMatch(e -> e.getAnnotationType() == repeatableMetaAnnotationType); + + } + +} + diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/config/builder/attributes/ConstraintAnnotationAttributeHandler.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/config/builder/attributes/ConstraintAnnotationAttributeHandler.java index 26ecd50d2..f85326b24 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/config/builder/attributes/ConstraintAnnotationAttributeHandler.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/config/builder/attributes/ConstraintAnnotationAttributeHandler.java @@ -20,43 +20,104 @@ import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.regex.Pattern; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertyResolver; +import com.antheminc.oss.nimbus.FrameworkRuntimeException; +import com.antheminc.oss.nimbus.context.BeanResolverStrategy; import com.antheminc.oss.nimbus.domain.config.builder.AnnotationAttributeHandler; +import com.antheminc.oss.nimbus.support.JustLogit; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; /** - * Implementation of AnnotationAttributeHandler that ensures all JSR Constraint - * attributes are filtered as needed. + * Implementation of {@link AnnotationAttributeHandler} that ensures all JSR + * {@link javax.validation.Constraint} attributes are filtered as needed. * * @author Tony Lopez * */ +@Getter +@RequiredArgsConstructor public class ConstraintAnnotationAttributeHandler implements AnnotationAttributeHandler { public static final String ATTRIBUTE_MESSAGE_NAME = "message"; public static final String ATTRIBUTE_MESSAGE_VALUE_DEFAULT = ""; + public static final String DEFAULT_VALIDATION_MESSAGES_PATH = "validationMessages"; public static final String JSR_DEFAULT_MESSAGE_REGEX = "\\{javax.validation.constraints.(.*).message\\}"; - - /* - * (non-Javadoc) - * @see com.anthem.oss.nimbus.core.domain.config.builder.AnnotationAttributeHandler#generateFrom(java.lang.reflect.AnnotatedElement, java.lang.annotation.Annotation) - */ + public static final String SPEL_CHAR_KEY = "$"; + public static final String REPLACE_REGEX = "\\{(?.*)\\}"; + public static final String ANNOTATION_PREFIX = "@"; + + private static final JustLogit LOG = new JustLogit(ConstraintAnnotationAttributeHandler.class); + + private final PropertyResolver propertyResolver; + private final BeanResolverStrategy beanResolver; + private final Environment env; + private final ResourceBundle defaultMessagesBundle; + + private Pattern replacePattern = Pattern.compile(REPLACE_REGEX); + + public ConstraintAnnotationAttributeHandler(BeanResolverStrategy beanResolver) { + this(beanResolver, DEFAULT_VALIDATION_MESSAGES_PATH); + } + + public ConstraintAnnotationAttributeHandler(BeanResolverStrategy beanResolver, + String defaultValidationMessagesPath) { + this.beanResolver = beanResolver; + this.propertyResolver = this.beanResolver.get(PropertyResolver.class); + this.env = this.beanResolver.get(Environment.class); + this.defaultMessagesBundle = ResourceBundle.getBundle(defaultValidationMessagesPath); + } + @Override public Map generateFrom(AnnotatedElement annotatedElement, Annotation annotation) { - final AnnotationAttributes annotationAttributes = AnnotationUtils.getAnnotationAttributes(annotatedElement, annotation, false, true); + final AnnotationAttributes annotationAttributes = AnnotationUtils.getAnnotationAttributes(annotatedElement, + annotation, false, true); final HashMap map = new HashMap<>(); - - for(final Entry entry: annotationAttributes.entrySet()) { - // Prefer empty-string over JSR defaults. - if (entry.getKey().equals(ATTRIBUTE_MESSAGE_NAME) && ((String) entry.getValue()).matches(JSR_DEFAULT_MESSAGE_REGEX)) { - map.put(entry.getKey(), ATTRIBUTE_MESSAGE_VALUE_DEFAULT); - } else { - map.put(entry.getKey(), entry.getValue()); + + for (final Entry entry : annotationAttributes.entrySet()) { + Object resolvedValue = entry.getValue(); + if (entry.getKey().equals(ATTRIBUTE_MESSAGE_NAME) + && ((String) entry.getValue()).matches(JSR_DEFAULT_MESSAGE_REGEX)) { + resolvedValue = this.resolveMessage((String) entry.getValue(), annotatedElement, annotation); } + map.put(entry.getKey(), resolvedValue); } return map; } + private String resolveMessage(String initialMessageValue, AnnotatedElement annotatedElement, + Annotation annotation) { + String key = this.replacePattern.matcher(initialMessageValue).replaceAll("${messageKey}"); + String overriddenDefault = this.propertyResolver.getProperty(key); + if (null != overriddenDefault) { + return overriddenDefault; + } + return this.resolveDefaultMessage(initialMessageValue, annotatedElement, annotation); + } + + private String resolveDefaultMessage(String initialMessageValue, AnnotatedElement annotatedElement, + Annotation annotation) { + String key = this.replacePattern.matcher(initialMessageValue).replaceAll("${messageKey}"); + try { + return this.defaultMessagesBundle.getString(key); + } catch (MissingResourceException mre) { + String annotationName = ANNOTATION_PREFIX + annotation.annotationType().getSimpleName(); + LOG.warn(() -> "Default message not defined for " + initialMessageValue + + ". It is likely validation logic is not supported for " + annotationName + + ". Please consider opening a feature request for " + annotationName + + " validation support or remove " + annotationName + " from " + annotatedElement + "."); + return initialMessageValue; + } catch (ClassCastException cce) { + throw new FrameworkRuntimeException("Unable to resolve default mesage for " + initialMessageValue, cce); + } + } } diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/AssociatedEntity.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/AssociatedEntity.java index c2bdbeaba..e38e8890c 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/AssociatedEntity.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/AssociatedEntity.java @@ -36,18 +36,18 @@ public Class clazz() default Class.class; - public AssociationType association() default AssociationType.none; + public AssociationType association() default AssociationType.NONE; public FetchType fetch() default FetchType.EAGER; public CascadeType cascade() default CascadeType.ALL; public enum AssociationType { - oneToOne, - oneToMany, - manyToOne, - manyToMany, - none; + ONE_TO_ONE, + ONE_TO_MANY, + MANY_TO_ONE, + MANY_TO_MANY, + NONE; } public enum FetchType { EAGER, diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/ConfigLoadException.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/ConfigLoadException.java index 1f5fcdabe..877e3cad4 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/ConfigLoadException.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/ConfigLoadException.java @@ -31,16 +31,4 @@ public ConfigLoadException(String message) { super(message); } - public ConfigLoadException(Throwable cause) { - super(cause); - } - - public ConfigLoadException(String message, Throwable cause) { - super(message, cause); - } - - public ConfigLoadException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } - } diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/Constants.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/Constants.java index e3e2b3f17..abca48c4e 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/Constants.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/Constants.java @@ -99,10 +99,13 @@ public enum Constants { SEARCH_REQ_PROJECT_MAPPING_MARKER("projection.mapsTo"), SEARCH_REQ_AGGREGATE_MARKER("aggregate"), + SEARCH_REQ_AGGREGATE_PIPELINE("pipeline"), SEARCH_REQ_AGGREGATE_COUNT("count"), SEARCH_REQ_FETCH_MARKER("fetch"), SEARCH_REQ_ORDERBY_MARKER("orderby"), + SEARCH_REQ_ORDERBY_DESC_MARKER("desc()"), + SEARCH_REQ_ORDERBY_ASC_MARKER("asc()"), SEARCH_REQ_WHERE_MARKER("where"), SEARCH_REQ_PAGINATION_SIZE("pageSize"), @@ -110,7 +113,10 @@ public enum Constants { SEARCH_REQ_PAGINATION_SORT_PROPERTY("sortBy"), SEARCH_NAMED_QUERY_DELIMTER("~~"), - SEARCH_NAMED_QUERY_RESULT("result"); + SEARCH_NAMED_QUERY_RESULT("result"), + HTTP_RESPONSEBODY_INTERCEPTOR_HEADER("responseBody"), + HTTP_RESPONSEBODY_INTERCEPTOR_HEADER_RAW("_raw"); + diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/Repo.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/Repo.java index c2021fe26..0ff040496 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/Repo.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/Repo.java @@ -21,6 +21,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + /** * @author Soham Chakravarti * @@ -58,6 +61,11 @@ public static boolean exists(Repo repo) { return repo!=null && repo.cache()!=Repo.Cache.rep_none; } } + + public enum Remote { + rep_remote_ws, + rep_remote_none; + } String alias() default ""; @@ -66,6 +74,8 @@ public static boolean exists(Repo repo) { Cache cache() default Cache.rep_device; + Remote remote() default Remote.rep_remote_ws; + boolean autoSave() default true; NamedNativeQuery[] namedNativeQueries() default {}; diff --git a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/ViewConfig.java b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/ViewConfig.java index e127bb0de..1be8f6e65 100644 --- a/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/ViewConfig.java +++ b/nimbus-core/src/main/java/com/antheminc/oss/nimbus/domain/defn/ViewConfig.java @@ -21,11 +21,13 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import javax.validation.constraints.Future; +import javax.validation.constraints.Past; + import com.antheminc.oss.nimbus.domain.Event; import com.antheminc.oss.nimbus.domain.defn.event.StateEvent.OnStateLoad; import com.antheminc.oss.nimbus.domain.defn.extension.ParamContext; - import lombok.Getter; import lombok.Setter; @@ -45,20 +47,13 @@ public class ViewConfig { /** - * ** - *

- * Accordion groups a collection of contents in tabs. + * **

Accordion groups a collection of contents in tabs. * - *

- * Expected Field Structure + *

Expected Field Structure * - *

- * Accordion will be rendered when annotating a field nested under one of the - * following components: - *

    - *
  • {@link Form}
  • - *
  • {@link Section}
  • - *
+ *

Accordion will be rendered when annotating a field nested under one + * of the following components:

  • {@link Form}
  • + *
  • {@link Section}
* */ @Retention(RetentionPolicy.RUNTIME) @@ -70,40 +65,33 @@ public class ViewConfig { String alias() default "Accordion"; /** - *

CSS classes added here will be added to a container element surrounding this component. - *

This can be used to apply additional styling, if necessary. + *

CSS classes added here will be added to a container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. */ String cssClass() default "panel-default"; boolean multiple() default false; boolean showExpandAll() default false; - + boolean showMessages() default false; } /** - *

- * AccordionTab is the section of Accordion. + *

AccordionTab contains a collection of contents for an individual + * section within an Accordion. * - *

- * Expected Field Structure + *

Expected Field Structure * - *

- * AccordionTab should be used to decorate a field in a class that has been - * decorated with Accordion. - *

- *

- * AccordionTab will render nested fields in the same manner declared directly - * under a {@link Form} component + *

AccordionTab should be used to decorate a field in a class that has + * been decorated with Accordion.

AccordionTab will render nested + * fields in the same manner declared directly under a {@link Form} + * component * - *

- * AccordionTab used within a {@link Section} component, should contain fields - * decorated with one or more of the following components: - *

* @since 1.1 */ @Retention(RetentionPolicy.RUNTIME) @@ -1741,6 +1867,26 @@ public static enum Type { @ViewStyle public @interface MenuPanel { + /** + *

Link Types that define the behavior of the {@link MenuPanel} + * component. + * @author Tony Lopez + * @since 1.1 + */ + public static enum Type { + /** + *

External links are links that navigate to another site, + * different from the single page application. + */ + EXTERNAL, + + /** + *

Internal links are links that navigate within the single page + * application. + */ + INTERNAL; + } + String alias() default "MenuPanel"; /** @@ -1784,39 +1930,21 @@ public static enum Type { * component. */ String url() default ""; - - /** - *

Link Types that define the behavior of the {@link MenuPanel} - * component. - * @author Tony Lopez - * @since 1.1 - */ - public static enum Type { - /** - *

Internal links are links that navigate within the single page - * application. - */ - INTERNAL, - - /** - *

External links are links that navigate to another site, - * different from the single page application. - */ - EXTERNAL; - } } - + /** - *

Renders a popup window with content defined by the nested fields - * within the field that is decorated with @Modal. + *

Modal is a container to display content in an overlay window. * *

Expected Field Structure * *

Modal will be rendered when annotating a field nested under one of the * following components:

  • {@link Tile}
* + *

Modal will render nested fields that are decorated with:

    + *
  • {@link Section}
+ * *

Notes:

  • Default contextual properties are set by - * ModalStateEventHandler during the OnStateLoad + * ModalStateEventHandler during the {@code OnStateLoad} * event.
* * @since 1.0 @@ -1840,8 +1968,9 @@ public enum Type { ParamContext context() default @ParamContext(enabled = true, visible = false); /** - *

CSS classes added here will be added to a container element surrounding this component. - *

This can be used to apply additional styling, if necessary. + *

CSS classes added here will be added to a container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. */ String cssClass() default ""; // new @@ -1882,8 +2011,9 @@ public enum Options { String alias() default "MultiGrid"; /** - *

CSS classes added here will be added to a container element surrounding this component. - *

This can be used to apply additional styling, if necessary. + *

CSS classes added here will be added to a container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. */ String cssClass() default "question-header"; @@ -1895,12 +2025,7 @@ public enum Options { } /** - *

Expected Field Structure - * - *

MultiSelect will be rendered when annotating a field nested under one - * of the following components:

  • {@link Form}
- * - *

MultiSelect should decorate an array or collection. + * * * @since 1.0 */ @@ -1910,9 +2035,12 @@ public enum Options { public @interface MultiSelect { String alias() default "MultiSelect"; + String cols() default ""; + /** - *

CSS classes added here will be added to a container element surrounding this component. - *

This can be used to apply additional styling, if necessary. + *

CSS classes added here will be added to a container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. */ String cssClass() default ""; @@ -1922,17 +2050,12 @@ public enum Options { boolean postEventOnChange() default false; - String cols() default ""; + boolean dataEntryField() default true; } /** - *

Expected Field Structure - * - *

MultiSelectCard will be rendered when annotating a field nested under - * one of the following components:

  • {@link Form}
- * - *

MultiSelectCard should decorate an array or collection. + * * * @since 1.0 */ @@ -1943,13 +2066,18 @@ public enum Options { String alias() default "MultiSelectCard"; /** - *

CSS classes added here will be added to a container element surrounding this component. - *

This can be used to apply additional styling, if necessary. + *

CSS classes added here will be added to a container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. */ String cssClass() default ""; + + boolean dataEntryField() default true; } /** + *

Page is a container component that groups a collection of contents. + * *

Expected Field Structure * *

Page will be rendered when annotating a field nested under one of the @@ -1967,8 +2095,9 @@ public enum Options { String alias() default "Page"; /** - *

CSS classes added here will be added to a container element surrounding this component. - *

This can be used to apply additional styling, if necessary. + *

CSS classes added here will be added to a container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. */ String cssClass() default ""; @@ -1977,6 +2106,8 @@ public enum Options { String imgSrc() default ""; String route() default ""; // remove + + boolean fixLayout() default false; } /** @@ -1996,6 +2127,8 @@ public enum Property { } /** + *

Paragraph is a container for displaying text content. + * *

Expected Field Structure * *

Paragraph will be rendered when annotating a field nested under one of @@ -2014,33 +2147,39 @@ public enum Property { String alias() default "Paragraph"; /** - *

CSS classes added here will be added to a container element surrounding this component. - *

This can be used to apply additional styling, if necessary. + *

CSS classes added here will be added to a container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. */ String cssClass() default ""; } /** + *

PickList is used to reorder items between different lists. + * *

Expected Field Structure * *

PickList will be rendered when annotating a field nested under one of * the following components:

  • {@link Form}
* - *

PickList should decorate a complex type, with its nested param annotated as {@link PickListSelected}. - *

Ex : + *

PickList should decorate a complex type, with its nested param + * annotated as {@link PickListSelected}.

Ex : + * *

-	 * 	@PickList(sourceHeader="Available Category", targetHeader="Selected Category")
-	 *  @Values(value=A_Category.class)
-	 * 	private PicklistType category; 
-	 * 	
-	 *  @Getter @Setter @Type(SomeClass.class)
-	 *	public static class PicklistType {
-	 *	@Values(value=AllCategory.class)
-	 *	@Path("category")
-	 *	@PickListSelected(postEventOnChange=true)
-	 *	private String[] selected; 
-	 *	}
-	 *	
+	 * @PickList(sourceHeader = "Available Category", targetHeader = "Selected Category")
+	 * @Values(value = A_Category.class)
+	 * private PicklistType category;
+	 * 
+	 * @Getter
+	 * @Setter
+	 * @Type(SomeClass.class)
+	 * public static class PicklistType {
+	 * 	@Values(value = AllCategory.class)
+	 * 	@Path("category")
+	 * 	@PickListSelected(postEventOnChange = true)
+	 * 	private String[] selected;
+	 * }
+	 * 
 	 * 
* * @since 1.0 @@ -2051,102 +2190,311 @@ public enum Property { public @interface PickList { String alias() default "PickList"; + String cols() default ""; + /** - *

CSS classes added here will be added to the container element surrounding this component. - *

This can be used to apply additional styling, if necessary. + *

CSS classes added here will be added to the container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. */ String cssClass() default ""; - + + boolean dataEntryField() default true; + String help() default ""; String labelClass() default "anthem-label"; boolean readOnly() default false; - String sourceHeader() default "SourceList"; - - String targetHeader() default "TargetList"; - /** - *

When {@code true}, the sort controls on source list are shown on the UI + *

When {@code true}, the sort controls on source list are shown on + * the UI */ boolean showSourceControls() default false; - + /** - *

When {@code true}, the sort controls on target list are shown on the UI + *

When {@code true}, the sort controls on target list are shown on + * the UI */ boolean showTargetControls() default false; - - String cols() default ""; + + String sourceHeader() default "SourceList"; + + String targetHeader() default "TargetList"; } - + /** *

Expected Field Structure * - *

PickListSelected will be rendered when annotating a field nested under one of the following components: - *

    - *
  • {@link PickList}
  • - *
+ *

PickListSelected will be rendered when annotating a field nested under + * one of the following components:

  • {@link PickList}
* - *

PickListSelected should decorate an array or collection - *

A comprehensive list of {@link @Values} on a given {@link PickList} should be annotated on the PickListSelected field - * so that a map for all code-label pairs will be available for the chosen items. + *

PickListSelected should decorate an array or collection

A + * comprehensive list of {@link @Values} on a given {@link PickList} should + * be annotated on the PickListSelected field so that a map for all + * code-label pairs will be available for the chosen items. * * @since 1.0 */ @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.FIELD}) + @Target({ ElementType.FIELD }) @ViewStyle public @interface PickListSelected { - + String alias() default "PickListSelected"; - + /** - *

When {@code true} and the value of this component is changed on the client, the updated - * value will be sent to the server. + *

When {@code true} and the value of this component is changed on + * the client, the updated value will be sent to the server. */ boolean postEventOnChange() default false; - + } /** - *

Expected Field Structure + *

Defines print configuration for a {@link ViewStyle} component. * - *

Radio will be rendered when annotating a field nested under one of the - * following components:

  • {@link Form}
+ *

{@code PrintConfig} should decorate a field that is also decorated + * with {@link Button}. * - *

Radio should decorate a field having a simple type. + *

Sample Usage * - * @since 1.0 + *

+	 * @Button(style = Button.Style.PRINT)
+	 * @PrintConfig(autoPrint = false)
+	 * private String print;
+	 * 
+ * + * @author Tony Lopez + * @since 1.1 + * */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD }) - @ViewStyle - public @interface Radio { - String alias() default "Radio"; + @ViewParamBehavior + public @interface PrintConfig { - String controlId() default ""; + /** + *

Whether or not to open the print dialog immediately after the + * print window/tab is opened. + */ + boolean autoPrint() default true; /** - *

CSS classes added here will be added to a container element surrounding this component. - *

This can be used to apply additional styling, if necessary. + *

Whether or not to close the opened browser window/tab containing + * the printable content after the user completes actions within the + * native browser print dialog. */ - String cssClass() default ""; + boolean closeAfterPrint() default true; - String help() default ""; + /** + *

The delay (in milliseconds) between when the browser opens a new + * window/tab containing the printable content and when the browser + * opens the native print dialog.

The {@code delay} will be applied + * only when {@link #useDelay()} is {@code true}. + */ + int delay() default 300; - String labelClass() default "anthem-label"; + /** + *

A relative URI of a stylesheet that should be applied to the + * printable content. The URI's provided are relative to the server + * context of the client application. For example, + * {@code "/styles/sheet1.css"} would resolve to: + * {@code http://localhost:8080/appcontext/styles/sheet1.css}

*Note: + * The protocol, host, port, and context are client specific.

If + * providing a very large stylesheet, it may be necessary to modify the + * {@link delay} property so that the stylesheet have time to load prior + * to the print actions taking place. + */ + String stylesheet() default ""; - String level() default "0"; + /** + *

Whether or not to include the single page application styles with + * the printable content.

This feature is experimental and may be + * removed in the future. + */ + boolean useAppStyles() default false; + + /** + *

Whether or not to use the {@link delay} setting.

If + * {@link #stylesheet()} is provided as a non-empty array, + * {@code useDelay} will be set to {@code true} regardless of the value + * set. + */ + boolean useDelay() default true; + } + + /** + *

Radio is an extension to standard radio button element. + * + *

Expected Field Structure + * + *

Radio will be rendered when annotating a field nested under one of the + * following components:

  • {@link Form}
+ * + *

Radio should decorate a field having a simple type. + * + * @since 1.0 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.FIELD }) + @ViewStyle + public @interface Radio { + String alias() default "Radio"; - boolean postEventOnChange() default false; - String cols() default ""; + String controlId() default ""; + + /** + *

CSS classes added here will be added to a container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. + */ + String cssClass() default ""; + + boolean dataEntryField() default true; + + String help() default ""; + + String labelClass() default "anthem-label"; + + String level() default "0"; + + boolean postEventOnChange() default false; + + } + + /** + *

RichText is a rich text editor. + * + *

Expected Field Structure + * + *

RichText will be rendered when annotating a field nested under one of + * the following components:

  • {@link Form}
+ * + *

RichText should decorate a field having a simple type. + * + * @since 1.2 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.FIELD }) + @ViewStyle + public @interface RichText { + + public enum ToolbarFeature { + ALIGN, BACKGROUND, BLOCKQUOTE, BOLD, CLEAN, CODE, CODE_BLOCK, COLOR, DIRECTION, FONT, HEADER, IMAGE, INDENT, + /** + *

Adds a combobox to the toolbar. Selected values from the + * combobox will be inserted into the editor's last known cursor + * position (at the start if untouched).

Values can be + * supplied by decorating the field with {@link Values}.

+ */ + VALUES_COMBOBOX, ITALIC, LINK, LIST, SCRIPT, SIZE, STRIKE, UNDERLINE, VIDEO; + } + + String alias() default "RichText"; + + /** + *

Drives the size of the component by how many columns it occupies + * on the UI. + */ + String cols() default ""; + + String controlId() default ""; + + /** + *

CSS classes added here will be added to a container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. + */ + String cssClass() default ""; + + /** + *

Whether or not this field should be considered a user-input field. + */ + boolean dataEntryField() default true; + + /** + *

Whitelist of formats to display, see here for available + * options. + */ + String formats() default ""; + + /** + *

Placeholder text to show when editor is empty. + */ + String placeholder() default ""; + + /** + *

When {@code true} and the value of this component is changed on + * the client, the updated value will be sent to the server. + */ + boolean postEventOnChange() default false; + + /** + *

Marks if this rich text box should be used in readonly mode. Using this mode will + * simply display a readonly text field with the rich text formatting preserved. + */ + boolean readOnly() default false; + + /** + *

The features to include when rendering the toolbar.

Features + * will be rendered in the order they are configured. + */ + ToolbarFeature[] toolbarFeatures() default { ToolbarFeature.HEADER, ToolbarFeature.FONT, ToolbarFeature.BOLD, + ToolbarFeature.ITALIC, ToolbarFeature.UNDERLINE, ToolbarFeature.STRIKE, ToolbarFeature.COLOR, + ToolbarFeature.BACKGROUND, ToolbarFeature.SCRIPT, ToolbarFeature.LIST, ToolbarFeature.INDENT, + ToolbarFeature.ALIGN, ToolbarFeature.LINK, ToolbarFeature.CLEAN }; + } + + /** + *

Fonts is a {@link ViewParamBehavior} that can be used to apply fonts + * to the decorated component.

+ * + * @since 1.2 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.FIELD, ElementType.TYPE }) + @ViewParamBehavior + public static @interface Fonts { + String[] value() default { "Serif", "Monospace" }; + } + + /** + *

Headings is a {@link ViewParamBehavior} that can be used to apply + * headings to the decorated component.

+ * + * @since 1.2 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.FIELD }) + @ViewParamBehavior + public static @interface Headings { + KeyValuePair[] value() default { @KeyValuePair(key = "Heading", value = "1"), + @KeyValuePair(key = "Subheading", value = "2") }; + } + + /** + *

KeyValuePair is a simple container annotation that stores key/value + * pair information.

+ * + * @since 1.2 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.FIELD }) + public static @interface KeyValuePair { + String key(); + + String value(); } /** + *

Section is a container component that groups a collection of contents. + * *

Expected Field Structure * *

Section will be rendered when annotating a field nested under one of @@ -2155,11 +2503,11 @@ public enum Property { * * *

Section will render nested fields that are decorated with:

    - *
  • {@link AccordionMain}
  • {@link Button}
  • + *
  • {@link Accordion}
  • {@link Button}
  • *
  • {@link ButtonGroup}
  • {@link CardDetail}
  • - *
  • {@link CardDetailsGrid}
  • {@link ComboBox}
  • + *
  • {@link CardDetailsGrid}
  • {@link Tab}
  • {@link ComboBox}
  • *
  • {@link Form}
  • {@link Grid}
  • {@link Link}
  • - *
  • {@link Menu}
  • {@link Paragraph}
  • + *
  • {@link Menu}
  • {@link Paragraph}
  • {@link Chart}
  • *
  • {@link StaticText}
  • {@link TextBox}
* * @since 1.0 @@ -2177,30 +2525,32 @@ public enum Type { String alias() default "Section"; /** - *

CSS classes added here will be added to a container element surrounding this component. - *

This can be used to apply additional styling, if necessary. + *

CSS classes added here will be added to a container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. */ String cssClass() default ""; - Type value() default Type.DEFAULT; // HEADER and LEFTBAR should be - // removed in future - String imgSrc() default ""; - + Image.Type imgType() default Image.Type.FA; + + Type value() default Type.DEFAULT; // HEADER and LEFTBAR should be + // removed in future } /** - *

The Signature component is used to capture a user's signature using user input in the form of - * a Data URL. + *

Signature is an HTML canvas element that can be used to capture + * signature content. * *

Expected Field Structure * *

Signature will be rendered when annotating a field nested under one of * the following components:

  • {@link Form}
* - *

Signature should decorate a field having a simple type. - *

Sample Usage: + *

Signature should decorate a field having a simple type.

Sample + * Usage: + * *

 	 * @Signature
 	 * private String userSignature;
@@ -2216,20 +2566,20 @@ public enum Type {
 	public @interface Signature {
 
 		/**
-		 * 

The strategy for how the signature drawing should be captured on the - * UI. + *

The strategy for how the signature drawing should be captured on + * the UI. */ public enum CaptureType { /** - *

Signature data is captured in between the mouse down and mouse up - * events. + *

Signature data is captured in between the mouse down and mouse + * up events. */ DEFAULT, /** - *

Signature data is captured upon the click event. Capturing will - * continue until the click event is invoked a second time. + *

Signature data is captured upon the click event. Capturing + * will continue until the click event is invoked a second time. */ ON_CLICK; } @@ -2243,45 +2593,63 @@ public enum CaptureType { * @see com.antheminc.oss.nimbus.domain.defn.ViewConfig.Signature.CaptureType */ CaptureType captureType() default CaptureType.DEFAULT; + /** *

The label value displayed on the "clear" button. */ String clearLabel() default "Clear"; + /** - *

CSS classes added here will be added to the container element surrounding this component. - *

This can be used to apply additional styling, if necessary. + *

CSS classes added here will be added to the container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. */ String cssClass() default ""; + /** - *

Method name from app_scripts.js is provided in scriptName, which will be triggered - * on click of "Get updated signature" button + *

Tells that the control is eligible for form validations. */ - String scriptName() default ""; + boolean dataEntryField() default true; + /** - *

The width of the signature canvas. + *

The width of the signature canvas. */ String height() default "60"; + /** - *

When {@code true}, the the label and help text will be hidden for this component. + *

When {@code true}, the the label and help text will be hidden for + * this component. */ boolean hidden() default false; + /** - *

When {@code true} and the value of this component is changed on the client, the updated - * value will be sent to the server. + *

When {@code true} and the value of this component is changed on + * the client, the updated value will be sent to the server. */ boolean postEventOnChange() default false; + + /** + *

Method name from app_scripts.js is provided in scriptName, which + * will be triggered on click of "Get updated signature" button + */ + String scriptName() default ""; + /** - *

To be used by the client as a unique identifier for this component. - *

THIS VALUE SHOULD NOT BE CHANGED! + *

To be used by the client as a unique identifier for this + * component.

THIS VALUE SHOULD NOT BE CHANGED! */ String type() default "signature"; + /** - *

The width of the signature canvas. + *

The width of the signature canvas. */ String width() default "345"; } /** + *

StaticText is a container for displaying html content or text "as is" + * to the UI. + * *

Expected Field Structure * *

StaticText will be rendered when annotating a field nested under one @@ -2301,21 +2669,15 @@ public enum CaptureType { String contentId() default ""; /** - *

CSS classes added here will be added to a container element surrounding this component. - *

This can be used to apply additional styling, if necessary. + *

CSS classes added here will be added to a container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. */ String cssClass() default ""; } /** - *

Expected Field Structure - * - *

SubHeader will be rendered when annotating a field nested under one of - * the following components:

  • Layout Domain
- * - *

SubHeader should decorate a field having a simple type. - * - * @since 1.0 + * */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD }) @@ -2324,8 +2686,9 @@ public enum CaptureType { String alias() default "SubHeader"; /** - *

CSS classes added here will be added to a container element surrounding this component. - *

This can be used to apply additional styling, if necessary. + *

CSS classes added here will be added to a container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. */ String cssClass() default "col-sm-6 pb-0 align-top"; // pb-0 is added // for the demo. @@ -2345,8 +2708,9 @@ public enum CaptureType { String alias() default "TabInfo"; /** - *

CSS classes added here will be added to a container element surrounding this component. - *

This can be used to apply additional styling, if necessary. + *

CSS classes added here will be added to a container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. */ String cssClass() default ""; @@ -2354,6 +2718,9 @@ public enum CaptureType { } /** + *

TextArea is a text input component that allows for a specified number + * of rows. + * *

Expected Field Structure * *

TextArea will be rendered when annotating a field nested under one of @@ -2373,14 +2740,19 @@ public enum CaptureType { public @interface TextArea { String alias() default "TextArea"; + String cols() default ""; + String controlId() default ""; /** - *

CSS classes added here will be added to a container element surrounding this component. - *

This can be used to apply additional styling, if necessary. + *

CSS classes added here will be added to a container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. */ String cssClass() default ""; + boolean dataEntryField() default true; + String help() default ""; boolean hidden() default false; @@ -2394,11 +2766,12 @@ public enum CaptureType { String rows() default "5"; String type() default "textarea"; - - String cols() default ""; + } /** + *

TextBox is a text input component. + * *

Expected Field Structure * *

TextBox will be rendered when annotating a field nested under one of @@ -2415,14 +2788,19 @@ public enum CaptureType { public @interface TextBox { String alias() default "TextBox"; + String cols() default ""; + String controlId() default ""; /** - *

CSS classes added here will be added to a container element surrounding this component. - *

This can be used to apply additional styling, if necessary. + *

CSS classes added here will be added to a container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. */ String cssClass() default ""; + boolean dataEntryField() default true; + String help() default ""; boolean hidden() default false; @@ -2434,12 +2812,105 @@ public enum CaptureType { boolean readOnly() default false; String type() default "text"; - - String cols() default ""; } + + + /** + *

InputMask is a text input component. + * + *

Expected Field Structure + * + *

InputMask will be rendered when annotating a field nested under one of + * the following components:

  • {@link Form}
  • + * + *

    InputMask should decorate a field having a simple type. + * + * @since 1.0 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.FIELD }) + @ViewStyle + public @interface InputMask { + String alias() default "InputMask"; + + boolean dataEntryField() default true; + + boolean postEventOnChange() default false; + + /** + *

    maskStyle can be used to define the restricted format user is expected to enter. + * For example maskStyle="99-9999" would let user enter only 6 numerical values in the i/p box. + */ + String mask() default ""; + + /** + *

    Please visit PrimeNG's website for details on the below parameter. + */ + + String slotChar() default "_"; + + /** + *

    Please visit PrimeNG's website for details on the below parameter. + */ + String charRegex() default "[A-Za-z]"; + + + /** + *

    Please visit PrimeNG's website for details on the below parameter. + */ + String maskPlaceHolder() default ""; + + } + + + + /** + *

    Tab groups a collection of TabPanels in tabs. + * + *

    Expected Field Structure + * + *

    Tab will be rendered when annotating a field nested under one + * of the following components:

    • {@link Tile}
    • + *
    • {@link Section}
    + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.FIELD }) + @ViewStyle + public @interface Tab { + + String alias() default "Tab"; + } + + + + /** + *

    TabPanel is used to display content for the tabs. + *

    TabPanel will render nested fields that are decorated with:

      + *
    • {@link Tab}
    • {@link Section}
    + * We can have a section within a TabPanel or even a Tab to create a nested structure. + * + *

    Expected Field Structure + * + *

    TabPanel will be rendered when annotating a field nested under one + * of the following components:

    • {@link Tab}
    + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.FIELD }) + @ViewStyle + public @interface TabPanel { + String alias() default "TabPanel"; + + boolean closable() default false; + + } + /** + *

    Tile is a container component that groups a collection of contents. + * *

    Expected Field Structure * *

    Tile will be rendered when annotating a field nested under one of the @@ -2447,7 +2918,7 @@ public enum CaptureType { * *

    Tile will render nested fields that are decorated with:

      *
    • {@link Header}
    • {@link Modal}
    • {@link Section}
    • - *
    • {@link Tile}
    + *
  • {@link Tile}
  • {@link Tab}
* * @since 1.0 */ @@ -2466,8 +2937,9 @@ public enum Size { String alias() default "Tile"; /** - *

CSS classes added here will be added to a container element surrounding this component. - *

This can be used to apply additional styling, if necessary. + *

CSS classes added here will be added to a container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. */ String cssClass() default ""; @@ -2476,6 +2948,66 @@ public enum Size { Size size() default Size.Large; } + /** + *

TreeGrid is is used to display hierarchical data in tabular format. + * + *

Expected Field Structure + * + *

TreeGrid will be rendered when annotating a field nested under one of + * the following components:

  • {@link Section}
  • + *
  • {@link Form}
+ * + *

TreeGrid will render nested fields that are decorated with:

    + *
  • {@link GridColumn}
  • {@link LinkMenu}
  • + *
  • {@link TreeGridChild}
+ * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.FIELD }) + @ViewStyle + public @interface TreeGrid { + String alias() default "TreeGrid"; + + /** + *

CSS classes added here will be added to a container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. + */ + String cssClass() default ""; + + boolean dataEntryField() default true; + } + + /** + *

TreeGridChild is the recursive child of {@link TreeGrid} and is used + * to display hierarchical data in tabular format. + * + *

Expected Field Structure + * + *

TreeGridChild will be rendered when annotating a field nested under + * one of the following components:

  • {@link TreeGrid}
+ * + *

{@code TreeGridChild} should decorate a field having a + * collection/array with a defined type. The type should match the + * collection element type of the parent field decorated with + * {@link TreeGrid}. Consequently, the rendered fields for + * {@code TreeGridChild} would be the same as those rendered for + * {@link TreeGrid}. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.FIELD }) + @ViewStyle + public @interface TreeGridChild { + String alias() default "TreeGridChild"; + + /** + *

CSS classes added here will be added to a container element + * surrounding this component.

This can be used to apply additional + * styling, if necessary. + */ + String cssClass() default ""; + } + /** * * @@ -2489,7 +3021,7 @@ public enum Size { } /** - * + *

ViewRoot is the entry point for a view domain definition. * * @since 1.0 */ @@ -2514,4 +3046,53 @@ public enum Size { public @interface ViewStyle { } + + /** + *

Chart is readonly component with some interaction(hide/show charts when the corresponding legend is clicked. + * + *

The projection object for the chart is List + * + *

Chart will be rendered when annotating a field nested under one of + * the following components: