package com.onsemi.middleware.camel;

import static java.util.stream.Collectors.*;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import com.onsemi.middleware.UECommon.model.ConnectionBeanDb;
import com.onsemi.middleware.camel.context.KaravanPropertySource;
import com.onsemi.middleware.ue.execeng.runtime.ConnectionProvision;
import com.onsemi.middleware.util.ONCryption;
import org.apache.camel.CamelContext;
import org.apache.camel.Consumer;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.Producer;
import org.apache.camel.Route;
import org.apache.camel.Service;
import org.apache.camel.ServiceStatus;
import org.apache.camel.ShutdownRunningTask;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.direct.DirectEndpoint;
import org.apache.camel.component.properties.PropertiesComponent;
import org.apache.camel.dsl.yaml.YamlRoutesBuilderLoader;
import org.apache.camel.model.Model;
import org.apache.camel.processor.aggregate.AggregateProcessor;
import org.apache.camel.spi.EventNotifier;
import org.apache.camel.spi.InflightRepository;
import org.apache.camel.spi.ManagementAgent;
import org.apache.camel.spi.ManagementStrategy;
import org.apache.camel.spi.RoutesBuilderLoader;
import org.apache.camel.spring.SpringCamelContext;
import org.apache.camel.support.ResourceHelper;
import org.apache.camel.support.service.BaseService;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationListener;

import com.onsemi.middleware.UECommon.cache.hazelcast.spring.event.BecameClusterLeaderEvent;
import com.onsemi.middleware.UECommon.cache.hazelcast.spring.event.ClusterLeaderChangeEvent;
import com.onsemi.middleware.UECommon.cache.hazelcast.spring.event.StoppedBeingClusterLeader;
import com.onsemi.middleware.UECommon.model.ConnectionJndiCompositeBean;
import com.onsemi.middleware.UECommon.model.InterfaceJcaps;
import com.onsemi.middleware.camel.context.ObjectSavingManagementAgent;
import com.onsemi.middleware.camel.context.connection.ComponentFactory;
import com.onsemi.middleware.camel.context.connection.FactoryRouter;
import com.onsemi.middleware.camel.context.connection.MdwPollingStrategy;
import com.onsemi.middleware.camel.context.connection.spring.UECamelBridgeJunitConfiguration;
import com.onsemi.middleware.camel.context.transaction.AllOrNothingTransactionManager;
import com.onsemi.middleware.camel.event.listeners.CamelRunIdGeneratorListener;
import com.onsemi.middleware.camel.event.listeners.FailureHandledListener;
import com.onsemi.middleware.camel.event.listeners.transactions.BaseTransactionHandler;
import com.onsemi.middleware.camel.event.listeners.transactions.TransactionHandler;
import com.onsemi.middleware.camel.exception.CamelRuntimeException;
import com.onsemi.middleware.camel.exception.StartupException;
import com.onsemi.middleware.camel.helper.MapBackedUpEndpointInject;
import com.onsemi.middleware.camel.management.OnsemiJmxManagementStrategyFactory;
import com.onsemi.middleware.camel.processor.spring.transaction.MDWTransactionPolicy;
import com.onsemi.middleware.cloud.config.conditional.JunitAutoDetection;
import com.onsemi.middleware.cloud.config.conditional.LocalDevelDetection;
import com.onsemi.middleware.cloud.config.utils.UECloudApp;
import com.onsemi.middleware.logging.dispatcher.LogDispatcher;
import com.onsemi.middleware.messaging.Constants;
import com.onsemi.middleware.messaging.InboundMessage;
import com.onsemi.middleware.ue.execeng.UExecClass;
import com.onsemi.middleware.ue.execeng.annotation.spring.InterfaceProperty;
import com.onsemi.middleware.ue.execeng.cluster.ClusterInformation;
import com.onsemi.middleware.ue.execeng.commons.MDWBeanFactory;
import com.onsemi.middleware.ue.execeng.commons.camel.AnnotationBeanPostProcessor;
import com.onsemi.middleware.ue.execeng.commons.camel.EndpointInject;
import com.onsemi.middleware.ue.execeng.commons.camel.UECamelBridge;
import com.onsemi.middleware.ue.execeng.commons.camel.UriInject;
import com.onsemi.middleware.ue.execeng.main.LegacySpringSupportingMainClass;
import com.onsemi.middleware.ue.execeng.main.UEClassLoaderBean;
import com.onsemi.middleware.ue.execeng.services.serviceInterfaces.INodeServiceRuns;

public class UECamelBridgeImpl implements UECamelBridge, BeanFactoryAware, ApplicationListener<ClusterLeaderChangeEvent> {
	public static final String LOG_EX_CAUGHT_EXCEPTION_WHEN_SHUTTING_DOWN_GRACEFULLY = "Caught exception when shutting down gracefully";
	@InterfaceProperty(camelOptions = CamelOptions.CAMEL_FORCE_SHUTDOWN_TIMEOUT)
	int forceShutdownTimeoutSec = 20;
	final String serviceShutdownThreadPoolName = "serviceShutdownTP";
	int serviceForceShutdownTimeoutSec = 60;
	@InterfaceProperty(camelOptions = CamelOptions.CAMEL_SHUTDOWN_DELAY)
	int camelShutdownDelay = 30;
	@InterfaceProperty(camelOptions = CamelOptions.CAMEL_AGGREGATOR_TIMEOUT)
	private int camelAggregatorTimeout = 3600000;
	static int SLEEP_PERIOD_WAIT_FOR_CAMEL_SEC = 5;
	private final Logger logger = LoggerFactory.getLogger(this.getClass());
	private int hadZeroOrSameInflightsXTimes = 0;
	private int SAFE_TO_QUIT_AFTER_X_TIMES_WITHOUT_INFLIGHTS_OR_WITH_SAME_INFL = 3;

	@Autowired
	private LogDispatcher log;

	@Autowired
	private ComponentFactory compFactory;

	@Autowired
	private CamelJndiNameCreator nameCreator;

	@Autowired
	private FactoryRouter factoryRouter;

	@Autowired
	protected FailureHandledListener failureHandledListener;

	@Autowired
	private InboundMessage inMssg;

	@Autowired
	private InterfaceJcaps anInterface;

	@Autowired
	private KaravanPropertySource karavanProperties;

	@Autowired
	private Optional<ClusterInformation> hazelcastFactory;

	//leave this here to force some lazy beans injection
	@Autowired
	private Optional<UECamelBridgeJunitConfiguration> optionalUECamelBridgeJunitConfiguration;


	@Autowired
	private OnsemiJmxManagementStrategyFactory managementStrategyFactory;

	@Value("${config.UERuntimeEngine.ue.camel.karavan.karavanYamlPath}")
	private String karavanPath;


	private INodeServiceRuns nodeServiceRuns;
	Thread stopThread;

	@Autowired
	public void setNodeServiceRuns(INodeServiceRuns nodeServiceRuns) {
		this.nodeServiceRuns = nodeServiceRuns;
	}

	@Autowired
	ConnectionProvision prov;

	protected BeanFactory beanFactory;
	private boolean shuttingDown = false;

	private java.util.function.Consumer<ClassLoader> setterOfCurrentThreadClassLoader = (cl) -> Thread.currentThread().setContextClassLoader(cl);

	@Override
	public MDWBeanFactory bridge(MDWBeanFactory bfFromRuntime, Long interfaceId) {
		BeanFactory bfToUse = unwrapBeanFactoryIfNeeded(bfFromRuntime);
		if (isLegacySpring(bfToUse)){
			forceInjectEndpointInject(beanFactory);
		}
		addRoutesFromRouteBuilders(bfToUse);
		startTransactionIfNeeded(bfToUse);
		executeConditionalStartup(bfToUse);
		if (new JunitAutoDetection().isJunit()){
			blockUntilCamelFinishes();
		}
		else {
			if (!anInterface.isServiceFlg()) {
				blockUntilCamelFinishes();
			}
		}
		return bfFromRuntime;
	}

	private void startTransactionIfNeeded(BeanFactory bfToUse) {
		try {
			TransactionHandler th = bfToUse.getBean(TransactionHandler.class);
			if (th instanceof BaseTransactionHandler) {
				((BaseTransactionHandler) th).startTransaction();
			}
		} catch (NoSuchBeanDefinitionException e) {
			// ignore - no TransactionHandler defined
		}
	}

	protected boolean isLegacySpring(BeanFactory bfToUse) {
		return bfToUse.getBean(UEClassLoaderBean.class).getMainClassOfInterface() instanceof LegacySpringSupportingMainClass;
	}

	protected void forceInjectEndpointInject(BeanFactory beanFactory) {
		LegacySpringSupportingMainClass legacySpringSupportingMainClass = (LegacySpringSupportingMainClass) beanFactory.getBean(UEClassLoaderBean.class).getMainClassOfInterface();
		UExecClass uExecClass = legacySpringSupportingMainClass.getMainClassPlainConstructedObject()
															   .get();
		ConfigurableListableBeanFactory listableBeanFactory = (ConfigurableListableBeanFactory) beanFactory;
		Optional<AnnotationBeanPostProcessor> processor = listableBeanFactory.getBeansOfType(AnnotationBeanPostProcessor.class).values()
																			 .stream()
																			 .filter(bp -> bp.getAnnotationClass().equals(EndpointInject.class))
																			 .findAny();
		if (processor.isPresent()){
			processor.get().postProcessBeforeInitialization(uExecClass, "mainClass");
		}
		else {
			throw new RuntimeException("No AnnotationBeanPostProcessor for EndpointInject - likely missing ioc library dependency");
		}
	}

	protected BeanFactory unwrapBeanFactoryIfNeeded(MDWBeanFactory bfFromRuntime) {
		try {
			return bfFromRuntime.unwrapSelf();
		}
		catch (Exception e){
			throw new RuntimeException(e);
		}
	}

	protected void executeConditionalStartup(BeanFactory bfFromRuntime) {
		if (anInterface.isServiceFlg()){
			if (isSingletonService()){
				if (!hazelcastFactory.isPresent() && !new LocalDevelDetection().isLocalDevel()){
					throw new RuntimeException("Could not start camel context - ClusterInformation (HZ factory) is not a @Component and this is not local devel. " +
													   "Yet you are trying to start a singleton service");
				}
				if ((hazelcastFactory.isPresent() && hazelcastFactory.get().isClusterLeader(false))
						|| new LocalDevelDetection().isLocalDevel()) {
					logger.info("Starting singleton service, node is cluster leader");
					startCamelContext(bfFromRuntime);
				}
				else {
					logger.info("Not starting service - node is not a cluster leader");
				}
			}
			else {
				startCamelContext(bfFromRuntime);
			}
		}
		else {
			startCamelContext(bfFromRuntime);
		}
	}

	private boolean isSingletonService() {
		return anInterface.isServiceFlg() && anInterface.isSingleton();
	}

	protected void blockUntilCamelFinishes() {
		while (!getCamelContext().getStatus()
								 .isStarted()) {
			try {
				if (failureHandledListener.isShutdownBecauseOfExceptionExcuted()){
					break;//camel context stopped in failure handler
				}
				Thread.sleep(3000l);
			} catch (InterruptedException e) {
				logger.error("Error sleeping in Camel before it starts");
				e.printStackTrace();
			}
		}
		if (getCamelContext().getRoutes().size() == 0){
			logger.info("Exiting blocking until camel finishes - it has no routes");
			return;
		}
		while (!getCamelContext().getStatus().equals(ServiceStatus.Stopped)){
			try {
				Thread.sleep(3000l);
			} catch (InterruptedException e) {
				logger.error("Error sleeping in Camel before it finishes");
				e.printStackTrace();
			}
		}
	}

	@NotNull
	protected CamelContext getCamelContext() {
		return beanFactory.getBean(CamelContext.class);
	}


	private void startCamelContext(BeanFactory bfFromRuntime) {
		CamelContext context = bfFromRuntime.getBean(CamelContext.class);
		setManagementStrategy(context);
		registerListeners(context, beanFactory.getBean(CamelRunIdGeneratorListener.class));
		InterfaceJcaps runningInterface = bfFromRuntime.getBean(InterfaceJcaps.class);
		

		try {
			if (context instanceof SpringCamelContext){
				SpringCamelContext.setNoStart(false);
			}
			context.start();
			context.getRouteController().startAllRoutes();
			if (!runningInterface.isServiceFlg()) {
				runCamelContextOnlyOnce(context);
			}
			Thread.sleep(3000);
			context.stop();
		} catch (Exception e) {
			throw new StartupException("Could not start Camel Context", e);
		}

	}

	private void setManagementStrategy(CamelContext context) {
		if (context.getManagementStrategy() == null || !ObjectSavingManagementAgent.class.isAssignableFrom(context.getManagementStrategy().getManagementAgent().getClass())){
			try {
				context.setManagementStrategy(this.managementStrategyFactory.create(context, new HashMap<>()));
			} catch (Exception e) {
				throw new StartupException("Cannot set management strategy", e);
			}
		}

	}

	private void registerListeners(CamelContext context, CamelRunIdGeneratorListener runIdGeneratorListener) {
		if (ListableBeanFactory.class.isAssignableFrom(beanFactory.getClass())) {
			((ListableBeanFactory) beanFactory).getBeansOfType(EventNotifier.class)
											   .values()
											   .stream()
											   .forEach(l -> context.getManagementStrategy()
																	.addEventNotifier(l));
			if (EventNotifier.class.isAssignableFrom(runIdGeneratorListener.getClass())){
				context.getManagementStrategy().addEventNotifier((EventNotifier) runIdGeneratorListener);
			}
		}
		else {
			log.error("No listeners registered","No listeners will be registered because beanFactory is not assignable to ListableBeanfactory. It is of class "+ beanFactory.getClass(), new RuntimeException());
		}
	}

	private void runCamelContextOnlyOnce(CamelContext context)  {
		String[] routeBuilders = ((DefaultListableBeanFactory) beanFactory).getBeanNamesForType(MDWRouteBuilder.class);
		if(context.getCamelContextExtension().getContextPlugin(Model.class).getRouteDefinitions().toArray().length == 0){
			if (routeBuilders.length == 0){
				throw new CamelRuntimeException("Wrong interface API - you need to have at least one class extending MDWRouteBuilder");
			}
			MDWRouteBuilder routeBuilder = (MDWRouteBuilder) beanFactory.getBean(routeBuilders[0]);
			if(routeBuilder.shouldAutoStop()){
				gracefulCamelShutdown();
			}
		}


	}
	public void gracefulCamelShutdown(boolean intended) {
		CamelContext context = getCamelContext();
		final String runId = inMssg.getProperty(Constants.RUN_ID);
		log.setRunId(runId);
		while (!allRoutesAreRunning(context)){
			try {
				Thread.sleep(UECamelBridgeImpl.SLEEP_PERIOD_WAIT_FOR_CAMEL_SEC);
			} catch (InterruptedException e) {
				throw new CamelRuntimeException("Exception sleeping waiting for all routes to be started");
			}
		}
		UECamelBridgeImpl self = this;
		stopThread = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					log.setRunId(runId);
					Thread.sleep(camelShutdownDelay * 1000);
					while (hasInflights(context)) {
						Thread.sleep(UECamelBridgeImpl.SLEEP_PERIOD_WAIT_FOR_CAMEL_SEC*1000);
						if (intended){
							logger.warn("Some exchanges still inflight. Dumped inflight exchanges {}"+
												context.getInflightRepository().browse().stream().map(self::dumpExchange).collect(Collectors.joining(" ::: ")));
							break;

						}
					}
					if (!(context.getStatus().isStopped() || context.getStatus().isStopping())){
						if (!shuttingDown) {
							logger.debug("Shutting Camel context");
							doExecuteShutdown();
						}
					}
				}
				catch (Throwable e){
					log.error("Could not stop camel context. Attempting graceful shutdown", "See stack trace", e);
					try {
						doExecuteShutdown();
					}
					catch (Throwable ex){
						log.error(LOG_EX_CAUGHT_EXCEPTION_WHEN_SHUTTING_DOWN_GRACEFULLY, "See stack trace. Force shutting down", e);
						forceShutdownCamel(getCamelContext());
					}
				}
			}


		});
		stopThread.setName(Thread.currentThread().getName() + "-CamelContextStopper");
		stopThread.start();
	}
	private String dumpExchange(InflightRepository.InflightExchange inflightExchange) {
		try {
			return "headers => " + BeanUtils.describe(inflightExchange.getExchange().getIn().getHeaders()) +
					"body => "+BeanUtils.describe(inflightExchange.getExchange().getIn().getBody());
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private boolean hasInflights(CamelContext context) {
		boolean hasInflights = context.getInflightRepository().browse().size() > 0;
		if (hasInflights){
			hadZeroOrSameInflightsXTimes = 0;
		}
		else {
			hadZeroOrSameInflightsXTimes++;
		}
		if (SAFE_TO_QUIT_AFTER_X_TIMES_WITHOUT_INFLIGHTS_OR_WITH_SAME_INFL < hadZeroOrSameInflightsXTimes){
			return false;
		}
		else {
			return true;
		}
	}

	protected void addRoutesFromRouteBuilders(BeanFactory bfFromRuntime){
 		String[] routeBuilders = ((ListableBeanFactory) bfFromRuntime).getBeanNamesForType(MDWRouteBuilder.class);
		List<ConnectionJndiCompositeBean> connections = prov.getInterfaceConnections();
		for(ConnectionJndiCompositeBean connectionComboBean : connections){
			if(ConnectionBeanDb.class.isAssignableFrom(connectionComboBean.getConnection().getClass())){
				loadDbConnectionProperties(connectionComboBean);
			}
			else loadConnectionProperties(connectionComboBean);
		}
		addKaravanPropertiesToCamelContext();
		loadYamlKaravanRoutes();
		for (String bean : routeBuilders) {
			RouteBuilder rb = (RouteBuilder) bfFromRuntime.getBean(bean);
			addRoutesForBuilder(rb, bfFromRuntime);
		}
	}

		private void loadDbConnectionProperties(ConnectionJndiCompositeBean connection){
			String subName = "jndi."+connection.getConnectionJndi().getName()+".connection";
			HashMap<String,String> list = karavanProperties.getPropertySubMap(subName);
			HashMap<String, String> url;
			try {
				Field field = connection.getConnection().getClass().getDeclaredField("url");
				field.setAccessible(true);
				url = parseJdbcConnection(field.get(connection.getConnection()).toString());
			} catch (Exception e) {
				logger.error("Error while parsing jdbc url for karavan property loading");
				throw new RuntimeException(e);
			}

			for(String placeholder: list.keySet().stream().map(key -> key.substring(key.lastIndexOf('.'))).toList()){
			switch(placeholder){
				case ".servername", ".port", ".dbname":
					karavanProperties.setProperty(subName+placeholder, url.get(placeholder));
					break;
				case ".username":
					karavanProperties.setProperty(subName+placeholder,connection.getConnection().getUid());
					break;
				case ".password":
					try{
					karavanProperties.setProperty(subName+placeholder, new ONCryption().decrypt(connection.getConnection().getPwd()));
					}catch (Exception e){
						logger.error("It was not possible to decrypt password from database from connection "+url.get(".servername "));
					}
					break;
				default:
                    logger.info("Placeholder {} is not set to be loaded, value for start will be {}", placeholder, karavanProperties.getProperty(placeholder));
			}
		}
	}
	private void loadConnectionProperties(ConnectionJndiCompositeBean connection){
		String subName = "jndi."+connection.getConnectionJndi().getName()+".connectionJndi";
		HashMap<String,String> list = karavanProperties.getPropertySubMap(subName);
		for(String placeholder: list.keySet().stream().map(key -> key.substring(key.lastIndexOf('.'))).toList()) {
			switch (placeholder) {
				case ".servername":
					karavanProperties.setProperty(subName + placeholder, getValueViaField(connection.getConnection(),"host")); //reflection
					break;
				case ".username":
					karavanProperties.setProperty(subName + placeholder, connection.getConnection().getUid());
					break;
				case ".password":
					try {
						karavanProperties.setProperty(subName + placeholder, new ONCryption().decrypt(connection.getConnection().getPwd()));
					}catch (Exception e){
						logger.error("It was not possible to decrypt password from database");
					}
					break;
				case ".directory":
					karavanProperties.setProperty(subName + placeholder, getValueViaField(connection.getConnectionJndi(),"directory")); //reflection
					break;
				case ".filename":
					karavanProperties.setProperty(subName + placeholder, getValueViaField(connection.getConnectionJndi(),"fileName")); //reflection
					break;
				default:
					logger.info("Placeholder {} is not set to be loaded, value for start will be {}", placeholder, karavanProperties.getProperty(placeholder));
			}
		}
	}
	private String getValueViaField(Object target, String fieldName) {
		try {
			Field field = target.getClass().getDeclaredField(fieldName);
			field.setAccessible(true);
			Object value = field.get(target);
			return value != null ? value.toString() : null;
		} catch (Exception e) {
			logger.warn("Could not access field '{}' on {}: {}", fieldName, target.getClass().getSimpleName(), e.getMessage());
			return null;
		}
	}

	private HashMap<String, String> parseJdbcConnection(String jdbcUrl) {
		HashMap<String, String> result = new HashMap<>();

		Pattern pattern = Pattern.compile(
				"(?:(?:@|//))([^:/?#]+)(?::(\\d+))?(?:[:/;](\\w[^?;]*))?",
				Pattern.CASE_INSENSITIVE
		);

		Matcher matcher = pattern.matcher(jdbcUrl);

		if (matcher.find()) {
			result.put("servername", matcher.group(1)); // host
			if (matcher.group(2) != null) result.put("port", matcher.group(2));
			if (matcher.group(3) != null) result.put("dbname", matcher.group(3));
		}

		Pattern paramPattern = Pattern.compile("(?i)(?:[?;&])(?:databaseName|dbname|sid|service)=([^&;]*)");
		Matcher paramMatcher = paramPattern.matcher(jdbcUrl);
		if (paramMatcher.find()) {
			result.put("dbname", paramMatcher.group(1));
		}

		return result;
	}

	private void loadYamlKaravanRoutes() {
		File folder = new File(karavanPath + anInterface.getName().toLowerCase());
		if (folderExists(folder)) {
			loadYamlFilesFromFolder(folder);
		}
	}

	private boolean folderExists(File folder) {
		return folder.exists() && folder.isDirectory();
	}

	private void loadYamlFilesFromFolder(File folder) {
		File[] yamlFiles = folder.listFiles((dir, name) -> name.endsWith(".yaml"));
		if (yamlFiles != null) {
			for (File yamlFile : yamlFiles) {
				loadYamlFile(yamlFile);
			}
		}
	}

	private void loadYamlFile(File yamlFile) {
		CamelContext context = getCamelContext();
		try {
			RoutesBuilderLoader loader = new YamlRoutesBuilderLoader();
			loader.setCamelContext(context);
			RouteBuilder builder = (RouteBuilder) loader.loadRoutesBuilder(ResourceHelper.resolveResource(context, "file:"+yamlFile.getAbsolutePath()));
			context.addRoutes(builder);

		} catch (Exception e) {
			throw new StartupException("Could not create route for file: " + yamlFile.getPath(), e);
		}
	}


	private void addKaravanPropertiesToCamelContext(){
		CamelContext context = getCamelContext();
		Properties properties = new Properties();
		properties.putAll(karavanProperties.getProperties());
		PropertiesComponent propertiesComponent = new PropertiesComponent();
		propertiesComponent.setInitialProperties(properties);
		context.setPropertiesComponent(propertiesComponent);
	}


	private void addRoutesForBuilder(RouteBuilder rb, BeanFactory bfFromRuntime) {
		try {
			setGentleRouteShutdownTaskForNonService(rb, bfFromRuntime);
			rb.addRoutesToCamelContext(bfFromRuntime.getBean(CamelContext.class));
		} catch (Exception e) {
			throw new StartupException("Could not create routes defined in " + rb.getClass().getName(), e);
		}

	}

	private void setGentleRouteShutdownTaskForNonService(RouteBuilder rb, BeanFactory bfFromRuntime) {
		if (!anInterface.isServiceFlg()) {
			rb.getRouteCollection().getRoutes()
					.forEach(rd -> rd.shutdownRunningTask(ShutdownRunningTask.CompleteAllTasks));
		}
	}



	private void installComponentToContext(org.apache.camel.Component component, EndpointInject annotation) {
		CamelContext context = this.beanFactory.getBean(CamelContext.class);
		context.addComponent(getContextNameForComponent(annotation, component), component);
	}

	private String getContextNameForComponent(EndpointInject annotation, org.apache.camel.Component camelComponent) {
		return getContextNameForComponent(annotation, camelComponent.getClass());
	}

	private String getContextNameForComponent(EndpointInject annotation, Class<?> camelComponentClass) {
		return nameCreator.buildJndiName(annotation.jndiName(),
				factoryRouter.route((Class<? extends org.apache.camel.Component>) camelComponentClass));
	}

	public Endpoint getEndpointFrom(ConnectionJndiCompositeBean compositeBean, String uriParams, Class<?> componentClass) {

		String jndiName = compositeBean.getConnectionJndi().getName();

		EndpointInject annotation = new EndpointInject() {
			@Override
			public String jndiName() {
				return jndiName;
			}

			@Override
			public String uriParams() {
				return uriParams;
			}

			@Override
			public Class<?> componentClass() {
				return componentClass;
			}

			@Override
			public Class<? extends Annotation> annotationType() {
				return EndpointInject.class;
			}
		};

		return obtainEndpoint(createNewComponent(jndiName, annotation), annotation, compositeBean);
	}


	public String obtainUri(UriInject annotation, ConnectionJndiCompositeBean composite){
		MapBackedUpEndpointInject mapBackedUpEndpointInject = new MapBackedUpEndpointInject(annotation.uriParams());
		mapBackedUpEndpointInject.setComponentClass(annotation.componentClass());
		return compFactory.getUri(mapBackedUpEndpointInject, composite);
	}


	public Endpoint obtainEndpoint(org.apache.camel.Component camelComponent, EndpointInject annotation,
			ConnectionJndiCompositeBean composite) {
		return compFactory.getEndpoint(camelComponent, annotation, composite);
	}

	public org.apache.camel.Component createNewComponent(String jndi, EndpointInject annotation) {
		org.apache.camel.Component camelComponent;
		Class<? extends org.apache.camel.Component> fldType = (Class<? extends org.apache.camel.Component>) annotation.componentClass();
		camelComponent = compFactory.getComponent(fldType, jndi, annotation);

		if (camelComponent.getCamelContext() == null) {
			camelComponent.setCamelContext(getCamelContext());
		}
		installComponentToContext(camelComponent, annotation);
		return camelComponent;
	}