jhattab commented on code in PR #651:
URL: https://github.com/apache/camel-karaf/pull/651#discussion_r2641138803


##########
components/camel-test/camel-test-blueprint-junit5/src/main/java/org/apache/camel/test/blueprint/CamelBlueprintTestSupport.java:
##########
@@ -0,0 +1,757 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.test.blueprint;
+
+import org.apache.aries.blueprint.compendium.cm.CmNamespaceHandler;
+import org.apache.camel.CamelContext;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.blueprint.CamelBlueprintHelper;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.component.properties.PropertiesComponent;
+import org.apache.camel.model.ModelCamelContext;
+import org.apache.camel.spi.Registry;
+import org.apache.camel.support.builder.xml.XMLConverterHelper;
+import org.apache.camel.test.junit5.*;
+import org.apache.camel.test.junit5.util.CamelContextTestHelper;
+import org.apache.camel.test.junit5.util.ExtensionHelper;
+import org.apache.camel.util.IOHelper;
+import org.apache.camel.util.KeyValueHolder;
+import org.apache.camel.util.StopWatch;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
+import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.blueprint.container.BlueprintEvent;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.nio.file.Files;
+import java.util.*;
+import java.util.jar.JarFile;
+
+/**
+ * Base class for OSGi Blueprint unit tests with Camel
+ */
+public abstract class CamelBlueprintTestSupport extends AbstractTestSupport
+        implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(CamelBlueprintTestSupport.class);
+
+    /** Name of a system property that sets camel context creation timeout. */
+    public static final String SPROP_CAMEL_CONTEXT_CREATION_TIMEOUT = 
"org.apache.camel.test.blueprint.camelContextCreationTimeout";
+
+    private static ThreadLocal<BundleContext> threadLocalBundleContext = new 
ThreadLocal<>();
+    private volatile BundleContext bundleContext;
+    private final Set<ServiceRegistration<?>> services = new LinkedHashSet<>();
+
+    private final StopWatch watch = new StopWatch();
+
+    @RegisterExtension
+    @Order(1)
+    public final ContextManagerExtension contextManagerExtension;
+    private CamelContextManager contextManager;
+
+    protected CamelBlueprintTestSupport() {
+        super(new TestExecutionConfiguration(), new 
CamelBlueprintContextConfiguration());
+
+        configureTest(testConfigurationBuilder);
+        configureContext(camelContextConfiguration);
+        contextManagerExtension = new 
ContextManagerExtension(testConfigurationBuilder, camelContextConfiguration);
+    }
+
+    /**
+     * Override this method if you don't want CamelBlueprintTestSupport create 
the test bundle
+     * @return includeTestBundle
+     * If the return value is true CamelBlueprintTestSupport creates the test 
bundle which includes blueprint configuration files
+     * If the return value is false CamelBlueprintTestSupport won't create the 
test bundle
+     */
+    protected boolean includeTestBundle() {
+        return true;
+    }
+
+    /**
+     * <p>Override this method if you want to start Blueprint containers 
asynchronously using the thread
+     * that starts the bundles itself.
+     * By default this method returns <code>true</code> which means Blueprint 
Extender will use thread pool
+     * (threads named "<code>Blueprint Extender: N</code>") to startup 
Blueprint containers.</p>
+     * <p>Karaf and Fuse OSGi containers use synchronous startup.</p>
+     * <p>Asynchronous startup is more in the <em>spirit</em> of OSGi and 
usually means that if everything works fine
+     * asynchronously, it'll work synchronously as well. This isn't always 
true otherwise.</p>
+     * @return <code>true</code> when blueprint containers are to be started 
asynchronously, otherwise <code>false</code>.
+     */
+    protected boolean useAsynchronousBlueprintStartup() {
+        return true;
+    }
+
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    protected BundleContext createBundleContext() throws Exception {
+        System.setProperty("org.apache.aries.blueprint.synchronous", 
Boolean.toString(!useAsynchronousBlueprintStartup()));
+
+        // load configuration file
+        String[] file = loadConfigAdminConfigurationFile();
+        String[][] configAdminPidFiles = new String[0][0];
+        if (file != null) {
+            if (file.length % 2 != 0) {  // This needs to return pairs of 
filename and pid
+                throw new IllegalArgumentException("The length of the String[] 
returned from loadConfigAdminConfigurationFile must divisible by 2, was " + 
file.length);
+            }
+            configAdminPidFiles = new String[file.length / 2][2];
+
+            int pair = 0;
+            for (int i = 0; i < file.length; i += 2) {
+                String fileName = file[i];
+                String pid = file[i + 1];
+                if (!new File(fileName).exists()) {
+                    throw new IllegalArgumentException("The provided file \"" 
+ fileName + "\" from loadConfigAdminConfigurationFile doesn't exist");
+                }
+                configAdminPidFiles[pair][0] = fileName;
+                configAdminPidFiles[pair][1] = pid;
+                pair++;
+            }
+        }

Review Comment:
   Good idea — I’ve done a small refactoring and adopted your proposal.



##########
components/camel-test/camel-test-blueprint-junit5/src/main/java/org/apache/camel/test/blueprint/CamelBlueprintTestSupport.java:
##########
@@ -0,0 +1,757 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.test.blueprint;
+
+import org.apache.aries.blueprint.compendium.cm.CmNamespaceHandler;
+import org.apache.camel.CamelContext;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.blueprint.CamelBlueprintHelper;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.component.properties.PropertiesComponent;
+import org.apache.camel.model.ModelCamelContext;
+import org.apache.camel.spi.Registry;
+import org.apache.camel.support.builder.xml.XMLConverterHelper;
+import org.apache.camel.test.junit5.*;
+import org.apache.camel.test.junit5.util.CamelContextTestHelper;
+import org.apache.camel.test.junit5.util.ExtensionHelper;
+import org.apache.camel.util.IOHelper;
+import org.apache.camel.util.KeyValueHolder;
+import org.apache.camel.util.StopWatch;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
+import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.blueprint.container.BlueprintEvent;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.nio.file.Files;
+import java.util.*;
+import java.util.jar.JarFile;
+
+/**
+ * Base class for OSGi Blueprint unit tests with Camel
+ */
+public abstract class CamelBlueprintTestSupport extends AbstractTestSupport
+        implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(CamelBlueprintTestSupport.class);
+
+    /** Name of a system property that sets camel context creation timeout. */
+    public static final String SPROP_CAMEL_CONTEXT_CREATION_TIMEOUT = 
"org.apache.camel.test.blueprint.camelContextCreationTimeout";
+
+    private static ThreadLocal<BundleContext> threadLocalBundleContext = new 
ThreadLocal<>();
+    private volatile BundleContext bundleContext;
+    private final Set<ServiceRegistration<?>> services = new LinkedHashSet<>();
+
+    private final StopWatch watch = new StopWatch();
+
+    @RegisterExtension
+    @Order(1)
+    public final ContextManagerExtension contextManagerExtension;
+    private CamelContextManager contextManager;
+
+    protected CamelBlueprintTestSupport() {
+        super(new TestExecutionConfiguration(), new 
CamelBlueprintContextConfiguration());
+
+        configureTest(testConfigurationBuilder);
+        configureContext(camelContextConfiguration);
+        contextManagerExtension = new 
ContextManagerExtension(testConfigurationBuilder, camelContextConfiguration);
+    }
+
+    /**
+     * Override this method if you don't want CamelBlueprintTestSupport create 
the test bundle
+     * @return includeTestBundle
+     * If the return value is true CamelBlueprintTestSupport creates the test 
bundle which includes blueprint configuration files
+     * If the return value is false CamelBlueprintTestSupport won't create the 
test bundle
+     */
+    protected boolean includeTestBundle() {
+        return true;
+    }
+
+    /**
+     * <p>Override this method if you want to start Blueprint containers 
asynchronously using the thread
+     * that starts the bundles itself.
+     * By default this method returns <code>true</code> which means Blueprint 
Extender will use thread pool
+     * (threads named "<code>Blueprint Extender: N</code>") to startup 
Blueprint containers.</p>
+     * <p>Karaf and Fuse OSGi containers use synchronous startup.</p>
+     * <p>Asynchronous startup is more in the <em>spirit</em> of OSGi and 
usually means that if everything works fine
+     * asynchronously, it'll work synchronously as well. This isn't always 
true otherwise.</p>
+     * @return <code>true</code> when blueprint containers are to be started 
asynchronously, otherwise <code>false</code>.
+     */
+    protected boolean useAsynchronousBlueprintStartup() {
+        return true;
+    }
+
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    protected BundleContext createBundleContext() throws Exception {
+        System.setProperty("org.apache.aries.blueprint.synchronous", 
Boolean.toString(!useAsynchronousBlueprintStartup()));
+
+        // load configuration file
+        String[] file = loadConfigAdminConfigurationFile();
+        String[][] configAdminPidFiles = new String[0][0];
+        if (file != null) {
+            if (file.length % 2 != 0) {  // This needs to return pairs of 
filename and pid
+                throw new IllegalArgumentException("The length of the String[] 
returned from loadConfigAdminConfigurationFile must divisible by 2, was " + 
file.length);
+            }
+            configAdminPidFiles = new String[file.length / 2][2];
+
+            int pair = 0;
+            for (int i = 0; i < file.length; i += 2) {
+                String fileName = file[i];
+                String pid = file[i + 1];
+                if (!new File(fileName).exists()) {
+                    throw new IllegalArgumentException("The provided file \"" 
+ fileName + "\" from loadConfigAdminConfigurationFile doesn't exist");
+                }
+                configAdminPidFiles[pair][0] = fileName;
+                configAdminPidFiles[pair][1] = pid;
+                pair++;
+            }
+        }
+
+        // fetch initial configadmin configuration if provided programmatically
+        Properties initialConfiguration = new Properties();
+        String pid = setConfigAdminInitialConfiguration(initialConfiguration);
+        if (pid != null) {
+            configAdminPidFiles = new 
String[][]{{prepareInitialConfigFile(initialConfiguration), pid}};
+        }
+
+        final String symbolicName = getClass().getSimpleName();
+        final BundleContext answer = 
CamelBlueprintHelper.createBundleContext(symbolicName, getBlueprintDescriptor(),
+            includeTestBundle(), getBundleFilter(), getBundleVersion(), 
getBundleDirectives(), configAdminPidFiles);
+
+        boolean expectReload = 
expectBlueprintContainerReloadOnConfigAdminUpdate();
+
+        // must register override properties early in OSGi containers
+        var extra = useOverridePropertiesWithPropertiesComponent();
+        if (extra != null) {
+            answer.registerService(PropertiesComponent.OVERRIDE_PROPERTIES, 
extra, null);
+        }
+
+        Map<String, KeyValueHolder<Object, Dictionary>> map = new 
LinkedHashMap<>();
+        addServicesOnStartup(map);
+
+        List<KeyValueHolder<String, KeyValueHolder<Object, Dictionary>>> 
servicesList = new LinkedList<>();
+        for (Map.Entry<String, KeyValueHolder<Object, Dictionary>> entry : 
map.entrySet()) {
+            servicesList.add(asKeyValueService(entry.getKey(), 
entry.getValue().getKey(), entry.getValue().getValue()));
+        }
+
+        addServicesOnStartup(servicesList);
+
+        for (KeyValueHolder<String, KeyValueHolder<Object, Dictionary>> item : 
servicesList) {
+            String clazz = item.getKey();
+            Object service = item.getValue().getKey();
+            Dictionary dict = item.getValue().getValue();
+            LOG.debug("Registering service {} -> {}", clazz, service);
+            ServiceRegistration<?> reg = answer.registerService(clazz, 
service, dict);
+            if (reg != null) {
+                services.add(reg);
+            }
+        }
+
+        // if blueprint XML uses <cm:property-placeholder> (any 
update-strategy and any default properties)
+        // - 
org.apache.aries.blueprint.compendium.cm.ManagedObjectManager.register() is 
called
+        // - ManagedServiceUpdate is scheduled in felix.cm
+        // - 
org.apache.felix.cm.impl.ConfigurationImpl.setDynamicBundleLocation() is called
+        // - CM_LOCATION_CHANGED event is fired
+        // - if BP was already created, it's <cm:property-placeholder> 
receives the event and
+        // - 
org.apache.aries.blueprint.compendium.cm.CmPropertyPlaceholder.updated() is 
called,
+        //   but no BP reload occurs
+        // we will however wait for BP container of the test bundle to become 
CREATED for the first time
+        // each configadmin update *may* lead to reload of BP container, if it 
uses <cm:property-placeholder>
+        // with update-strategy="reload"
+
+        // we will gather timestamps of BP events. We don't want to be fooled 
but repeated events related
+        // to the same state of BP container
+        Set<Long> bpEvents = new HashSet<>();
+
+        CamelBlueprintHelper.waitForBlueprintContainer(bpEvents, answer, 
symbolicName, BlueprintEvent.CREATED, null);
+
+        // must reuse props as we can do both load from .cfg file and override 
afterwards
+        final Dictionary props = new Properties();
+
+        // allow end user to override properties
+        pid = useOverridePropertiesWithConfigAdmin(props);
+        if (pid != null) {
+            // we will update the configuration again
+            ConfigurationAdmin configAdmin = 
CamelBlueprintHelper.getOsgiService(answer, ConfigurationAdmin.class);
+            // passing null as second argument ties the configuration to 
correct bundle.
+            // using single-arg method causes:
+            // *ERROR* Cannot use configuration xxx.properties for 
[org.osgi.service.cm.ManagedService, id=N, bundle=N/jar:file:xyz.jar!/]: No 
visibility to configuration bound to felix-connect
+            final Configuration config = configAdmin.getConfiguration(pid, 
null);
+            if (config == null) {
+                throw new IllegalArgumentException("Cannot find configuration 
with pid " + pid + " in OSGi ConfigurationAdmin service.");
+            }
+            // lets merge configurations
+            Dictionary<String, Object> currentProperties = 
config.getProperties();
+            final Dictionary newProps = new Properties();
+            if (currentProperties == null) {
+                currentProperties = newProps;
+            }
+            for (Enumeration<String> ek = currentProperties.keys(); 
ek.hasMoreElements();) {
+                String k = ek.nextElement();
+                newProps.put(k, currentProperties.get(k));
+            }
+            for (String p : ((Properties) props).stringPropertyNames()) {
+                newProps.put(p, ((Properties) props).getProperty(p));
+            }
+
+            LOG.info("Updating ConfigAdmin {} by overriding properties {}", 
config, newProps);
+            if (expectReload) {
+                CamelBlueprintHelper.waitForBlueprintContainer(bpEvents, 
answer, symbolicName, BlueprintEvent.CREATED, new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            config.update(newProps);
+                        } catch (IOException e) {
+                            throw new RuntimeException(e.getMessage(), e);
+                        }
+                    }
+                });
+            } else {
+                config.update(newProps);
+            }
+        }
+
+        return answer;
+    }
+
+    @BeforeEach
+    public void setUp() throws Exception {
+        System.setProperty("skipStartingCamelContext", "true");
+        System.setProperty("registerBlueprintCamelContextEager", "true");
+
+        if (isCreateCamelContextPerClass()) {
+            // test is per class, so only setup once (the first time)
+            boolean first = threadLocalBundleContext.get() == null;
+            if (first) {
+                threadLocalBundleContext.set(createBundleContext());
+            }
+            bundleContext = threadLocalBundleContext.get();
+        } else {
+            bundleContext = createBundleContext();
+        }
+
+        ExtensionHelper.hasUnsupported(getClass());
+
+        setupResources();
+
+        contextManager = contextManagerExtension.getContextManager();
+        contextManager.createCamelContext(this);
+        context = contextManager.context();
+
+
+
+        // only start timing after all the setup
+        watch.restart();
+
+        // we don't have to wait for BP container's OSGi service - we've 
already waited
+        // for BlueprintEvent.CREATED
+
+        // start context when we are ready
+        LOG.debug("Starting CamelContext: {}", context.getName());
+        if (isUseAdviceWith()) {
+            LOG.info("Skipping starting CamelContext as isUseAdviceWith is set 
to true.");
+        } else {
+            context.start();
+        }
+    }
+
+    /**
+     * Override this method to add services to be registered on startup.
+     * <p/>
+     * You can use the builder methods {@link #asService(Object, Dictionary)}, 
{@link #asService(Object, String, String)}
+     * to make it easy to add the services to the map.
+     */
+    protected void addServicesOnStartup(Map<String, KeyValueHolder<Object, 
Dictionary>> services) {
+        // noop
+    }
+
+    /**
+     * This method may be overriden to instruct BP test support that BP 
container will reloaded when
+     * Config Admin configuration is updated. By default, this is expected, 
when blueprint XML definition
+     * contains <code>&lt;cm:property-placeholder persistent-id="PID" 
update-strategy="reload"&gt;</code>
+     */
+    protected boolean expectBlueprintContainerReloadOnConfigAdminUpdate() {
+        boolean expectedReload = false;
+        DocumentBuilderFactory dbf = new 
XMLConverterHelper().createDocumentBuilderFactory();
+        try {
+            // cm-1.0 doesn't define update-strategy attribute
+            Set<String> cmNamesaces = new HashSet<>(Arrays.asList(
+                    CmNamespaceHandler.BLUEPRINT_CM_NAMESPACE_1_1,
+                    CmNamespaceHandler.BLUEPRINT_CM_NAMESPACE_1_2,
+                    CmNamespaceHandler.BLUEPRINT_CM_NAMESPACE_1_3
+            ));
+            for (URL descriptor : 
CamelBlueprintHelper.getBlueprintDescriptors(getBlueprintDescriptor())) {
+                DocumentBuilder db = dbf.newDocumentBuilder();
+                try (InputStream is = descriptor.openStream()) {
+                    Document doc = db.parse(is);
+                    NodeList nl = doc.getDocumentElement().getChildNodes();
+                    for (int i = 0; i < nl.getLength(); i++) {
+                        Node node = nl.item(i);
+                        if (node instanceof Element) {
+                            Element pp = (Element) node;

Review Comment:
   Done



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to