org.kie.spring.KModuleBeanFactoryPostProcessor Maven / Gradle / Ivy
/*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* 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 org.kie.spring;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Map;
import org.drools.compiler.kie.builder.impl.ClasspathKieProject;
import org.drools.compiler.kie.builder.impl.InternalKieModule;
import org.drools.compiler.kie.builder.impl.KieBuilderImpl;
import org.drools.compiler.kie.builder.impl.KieRepositoryImpl;
import org.drools.compiler.kproject.ReleaseIdImpl;
import org.drools.compiler.kproject.models.KieBaseModelImpl;
import org.drools.compiler.kproject.models.KieModuleModelImpl;
import org.drools.compiler.kproject.models.KieSessionModelImpl;
import org.kie.api.KieServices;
import org.kie.api.builder.ReleaseId;
import org.kie.api.builder.model.KieModuleModel;
import org.kie.api.builder.model.KieSessionModel;
import org.kie.api.conf.DeclarativeAgendaOption;
import org.kie.api.conf.EqualityBehaviorOption;
import org.kie.api.conf.EventProcessingOption;
import org.kie.api.runtime.conf.ClockTypeOption;
import org.kie.spring.factorybeans.KBaseFactoryBean;
import org.kie.spring.factorybeans.KModuleFactoryBean;
import org.kie.spring.factorybeans.KSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
@Component("kiePostProcessor")
public class KModuleBeanFactoryPostProcessor implements BeanFactoryPostProcessor, ApplicationContextAware {
private static final Logger log = LoggerFactory.getLogger(KModuleBeanFactoryPostProcessor.class);
/* URLs only contain forward slashes - '/'
* See https://docs.oracle.com/javase/6/docs/api/java/net/URL.html for more info. */
private static final String WEB_INF_CLASSES_URL_SUFFIX = "WEB-INF/classes/";
/* Paths do contain OS-specific separators */
private static final String WEB_INF_CLASSES_PATH_SUFFIX = "WEB-INF" + File.separator + "classes";
/**
* Root URL of the KieModule which is associated with the Spring app context.
*
* After transforming the URL to a filesystem path it is used as base dir for the KieModule.
*
* Example: "file:/some-path/target/test-classes".
*/
protected URL kModuleRootUrl;
protected ReleaseId releaseId;
private ApplicationContext context;
public KModuleBeanFactoryPostProcessor() {
}
public KModuleBeanFactoryPostProcessor(URL kModuleRootUrl, ApplicationContext context) {
this.kModuleRootUrl = kModuleRootUrl;
this.context = context;
}
public KModuleBeanFactoryPostProcessor(URL kModuleRootUrl) {
this.kModuleRootUrl = kModuleRootUrl;
}
public URL getkModuleRootUrl() {
return kModuleRootUrl;
}
public void setReleaseId(ReleaseId releaseId) {
this.releaseId = releaseId;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
log.info(":: BeanFactoryPostProcessor::postProcessBeanFactory called ::");
String kModuleRootPath = parseKModuleRootPath(kModuleRootUrl);
if (releaseId == null && kModuleRootPath != null) {
String pomProperties = null;
if (kModuleRootPath.endsWith(WEB_INF_CLASSES_PATH_SUFFIX)) {
String configFilePathForWebApps = kModuleRootPath.substring(0, kModuleRootPath.indexOf(WEB_INF_CLASSES_PATH_SUFFIX));
pomProperties = ClasspathKieProject.getPomProperties(configFilePathForWebApps);
}
if (pomProperties == null) {
pomProperties = ClasspathKieProject.getPomProperties(kModuleRootPath);
}
if (pomProperties != null) {
releaseId = ReleaseIdImpl.fromPropertiesString(pomProperties);
} else {
releaseId = KieRepositoryImpl.INSTANCE.getDefaultReleaseId();
}
log.info("Found project with releaseId: " + releaseId);
}
if (releaseId == null) {
releaseId = KieRepositoryImpl.INSTANCE.getDefaultReleaseId();
}
for (String beanDef : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDef);
if (beanDefinition.getBeanClassName() != null && beanDefinition.getBeanClassName().equalsIgnoreCase(KModuleFactoryBean.class.getName())) {
KieModuleModel kieModuleModel = fetchKieModuleModel(beanFactory);
addKieModuleToRepo(kieModuleModel);
}
}
}
private String parseKModuleRootPath(URL kModuleRootUrl) {
return ClasspathKieProject.fixURLFromKProjectPath(kModuleRootUrl);
}
private void addKieModuleToRepo(KieModuleModel kieProject) {
KieBuilderImpl.setDefaultsforEmptyKieModule(kieProject);
InternalKieModule kJar = createKieModule(kieProject);
if ( kJar != null ) {
KieServices ks = KieServices.Factory.get();
log.info("Adding KieModule from " + parseKModuleRootPath(kModuleRootUrl) + " to repository.");
ks.getRepository().addKieModule(kJar);
}
}
protected InternalKieModule createKieModule(KieModuleModel kieProject) {
String rootPath = parseKModuleRootPath(kModuleRootUrl);
if (rootPath.lastIndexOf(':') >= 2) { // avoid to trucate Windows paths like C:\my\folder\...
rootPath = rootPath.substring(rootPath.lastIndexOf(':') + 1);
}
return ClasspathKieProject.createInternalKieModule(kieProject, releaseId, rootPath);
}
private KieModuleModel fetchKieModuleModel(ConfigurableListableBeanFactory beanFactory) {
KieModuleModelImpl kieModuleModel = new KieModuleModelImpl();
addKieBaseModels(beanFactory, kieModuleModel);
return kieModuleModel;
}
private void addKieBaseModels(ConfigurableListableBeanFactory beanFactory, KieModuleModelImpl kieModuleModel) {
BeanExpressionContext context = new BeanExpressionContext(beanFactory, null);
for (String beanDef : beanFactory.getBeanDefinitionNames()){
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDef);
if ( beanDefinition.getBeanClassName() != null && beanDefinition.getBeanClassName().equalsIgnoreCase(KBaseFactoryBean.class.getName())){
KieBaseModelImpl kBase = new KieBaseModelImpl();
kBase.setKModule(kieModuleModel);
kBase.setName( getPropertyValue( beanDefinition, "kBaseName" ));
kBase.setDefault( "true".equals( getPropertyValue(beanDefinition, "def") ) );
String packages = getPropertyValue( beanDefinition, "packages" );
if ( !packages.isEmpty() ) {
packages = checkAndResolveSpringExpression(beanFactory, context, packages);
for ( String pkg : packages.split( "," ) ) {
kBase.addPackage( pkg.trim() );
}
}
String includes = getPropertyValue( beanDefinition, "includes" );
if ( !includes.isEmpty() ) {
includes = checkAndResolveSpringExpression(beanFactory, context, includes);
for ( String include : includes.split( "," ) ) {
kBase.addInclude(include.trim());
}
}
String eventMode = getPropertyValue(beanDefinition, "eventProcessingMode");
if ( !eventMode.isEmpty() ) {
eventMode = checkAndResolveSpringExpression(beanFactory, context, eventMode);
kBase.setEventProcessingMode( EventProcessingOption.determineEventProcessingMode(eventMode) );
}
String equalsBehavior = getPropertyValue(beanDefinition, "equalsBehavior");
if ( !equalsBehavior.isEmpty() ) {
equalsBehavior = checkAndResolveSpringExpression(beanFactory, context, equalsBehavior);
kBase.setEqualsBehavior( EqualityBehaviorOption.determineEqualityBehavior(equalsBehavior) );
}
String declarativeAgenda = getPropertyValue(beanDefinition, "declarativeAgenda");
if ( !declarativeAgenda.isEmpty() ) {
declarativeAgenda = checkAndResolveSpringExpression(beanFactory, context, declarativeAgenda);
kBase.setDeclarativeAgenda(DeclarativeAgendaOption.determineDeclarativeAgenda(declarativeAgenda));
}
String scope = getPropertyValue(beanDefinition, "scope");
if ( !scope.isEmpty() ) {
scope = checkAndResolveSpringExpression(beanFactory, context, scope);
kBase.setScope( scope.trim() );
}
kieModuleModel.getRawKieBaseModels().put( kBase.getName(), kBase );
beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue("releaseId", releaseId));
addKieSessionModels(beanFactory, kBase);
}
}
}
protected String checkAndResolveSpringExpression(ConfigurableListableBeanFactory beanFactory, BeanExpressionContext context, String expression) {
if ( expression.startsWith("#{") && expression.endsWith("}")) {
return (String) beanFactory.getBeanExpressionResolver().evaluate(expression, context);
}
return expression;
}
private String getPropertyValue(BeanDefinition beanDefinition, String propertyName) {
PropertyValue propertyValue = beanDefinition.getPropertyValues().getPropertyValue(propertyName);
return propertyValue != null ? (String) propertyValue.getValue() : "";
}
private void addKieSessionModels(ConfigurableListableBeanFactory beanFactory, KieBaseModelImpl kBase) {
for (String beanDef : beanFactory.getBeanDefinitionNames()){
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDef);
if ( beanDefinition.getBeanClassName() != null && beanDefinition.getBeanClassName().equalsIgnoreCase(KSessionFactoryBean.class.getName())){
String kBaseName = getPropertyValue(beanDefinition, "kBaseName");
if ( kBase.getName().equalsIgnoreCase(kBaseName)) {
String name = getPropertyValue(beanDefinition, "name");
String type = getPropertyValue(beanDefinition, "type");
KieSessionModelImpl kSession = new KieSessionModelImpl(kBase, name);
kSession.setType(!type.isEmpty() ? KieSessionModel.KieSessionType.valueOf(type.toUpperCase()) : KieSessionModel.KieSessionType.STATEFUL);
Map rawKieSessionModels = kBase.getRawKieSessionModels();
rawKieSessionModels.put(kSession.getName(), kSession);
beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue("releaseId", releaseId));
kSession.setDefault( "true".equals( getPropertyValue(beanDefinition, "def") ) );
String clockType = getPropertyValue(beanDefinition, "clockType");
if ( !clockType.isEmpty() ) {
kSession.setClockType( ClockTypeOption.get(clockType) );
}
String scope = getPropertyValue(beanDefinition, "scope");
if ( !scope.isEmpty() ) {
kSession.setScope( scope.trim() );
}
}
}
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
try {
kModuleRootUrl = tryGetRootUrlForEapContext(applicationContext.getClassLoader().getResources("/"));
// in case the kModuleRootUrl is still null at this point, the assumption is we are not running on EAP
// so we just get the url from the classpath
if (kModuleRootUrl == null) {
kModuleRootUrl = tryGetRootUrlFromContextFile(applicationContext);
}
if (kModuleRootUrl == null) {
try {
kModuleRootUrl = applicationContext.getResource("classpath:/").getURL();
} catch (FileNotFoundException e) {
// a special case for aries-blueprint-spring, where there are no spring-dm resource helpers
kModuleRootUrl = applicationContext.getResource("classpath:/META-INF/MANIFEST.MF").getURL();
if (kModuleRootUrl != null && "bundle".equals(kModuleRootUrl.getProtocol())) {
kModuleRootUrl = new URL(kModuleRootUrl, "..");
}
}
}
} catch (IOException e) {
throw new RuntimeException("Error while trying to get root URL for the application context " +
applicationContext.getDisplayName(), e);
}
log.debug("KieModule root URL (based on application context {}): {}", applicationContext.getDisplayName(), kModuleRootUrl);
}
private URL tryGetRootUrlFromContextFile(ApplicationContext applicationContext) {
if ( applicationContext instanceof BeanDefinitionRegistry ) {
String[] kmoduleBeanNames = applicationContext.getBeanNamesForType( this.getClass() );
if (kmoduleBeanNames.length != 1) {
return null;
}
try {
AbstractBeanDefinition contextBean = (AbstractBeanDefinition) ( (BeanDefinitionRegistry) applicationContext ).getBeanDefinition( kmoduleBeanNames[0] );
Resource contextResource = contextBean.getResource();
String contextUrl = contextResource.getURL().toString();
String classPathUrl = contextUrl.substring( 0, contextUrl.length() - (contextResource.getFilename().length() + 1) );
return new URL( classPathUrl );
} catch (Exception e) { }
}
return null;
}
/**
* This is a HACK for web applications deployed to EAP/WildFly which are using kie-spring.
*
* The method tries to figure out the root URL based on the provided URL enumeration. It covers (at least) the
* following two use cases:
* 1) kie-spring deployed together (bundled) with Spring webapp (inside WEB-INF/lib)
* 2) kie-spring deployed as EAP module + Spring webapp depending on that module
*
* First of all it tries to determine if it is running on EAP, based on EAP specific resource URL. If that is the
* case it looks for the "WEB-INF/classes" dir and return that as an VFS URL (vfs:/...). Later on, this URL
* needs to be translated to a real filesystem path. This is one by {@link ClasspathKieProject#fixURLFromKProjectPath(URL)}
*
* @param rootUrls Classpath root URLs
* @return NULL is case the code is not running on EAP, otherwise root URL of the webapp context (that is webapp's WEB-INF/classes)
*/
URL tryGetRootUrlForEapContext(Enumeration rootUrls) {
boolean containsEapSpecificUrl = false;
boolean containsWebInfClassesUrl = false;
URL webInfClassesUrl = null;
while (rootUrls.hasMoreElements()) {
URL url = rootUrls.nextElement();
if (isEapSpecificUrl(url)) {
containsEapSpecificUrl = true;
} else if (url.toString().endsWith(WEB_INF_CLASSES_URL_SUFFIX)) {
containsWebInfClassesUrl = true;
webInfClassesUrl = url;
}
}
if (containsEapSpecificUrl && containsWebInfClassesUrl) {
return webInfClassesUrl;
} else {
return null;
}
}
/**
* Check if the provided URL is EAP specific URL. The method is used to check if the
* code running inside EAP. Yes, this is a very ugly hack, but there does not seem a better way around.
*
* @param url URL to check
* @return true in case the enumeration contains EAP specific URL, otherwise false
*/
boolean isEapSpecificUrl(URL url) {
return url.toString().endsWith("service-loader-resources/");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy