[ 
https://issues.apache.org/jira/browse/FELIX-4847?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=14487040#comment-14487040
 ] 

Tuomas Kiviaho commented on FELIX-4847:
---------------------------------------

In my case the temporal service remained unavailable (used dm shell to verify 
this) until the actual service arrived. This is the behavior expected from 
{{required}} temporal service dependency and I see now that tampering with what 
{{isAvailable()}} returns - as per 1) proposal - would break the backwards 
compatibility. I did not understand that the intention was to actually wait 
until there is a service before dependency becomes available. 

Therefore my only working proposal is 2) that is allowing the temporal service 
to be optional as well. What I'd like to happen in this mode is for the 
dependency to be available immediately and for the proxy to block/wait for the 
services first appearance if user happens to access it prematurely. This is 
indeed what would happen if optionality would not have been explicitly 
forbidden in {{setRequired}} method. DefaultNullObject used by non-temporal 
service dependency instead would return null immediately causing NPE in my 
usecase.

This is a snippet from my PDE JUnit test case (magic happens inside a rule 
below that understands DM 3.x annotations) that relies on optional temporal 
service. Otherwise I'd have to manually track/wait Foobar service after 
asynchronous CM update (with a custom default impl)
 
{code}
public class FoobarIT
{
    @Rule
    public TestRule testRule = new DependencyManagerTestRule() { protected 
Object getWrapped() { return FoobarIT.this; }};

    @Inject
    private BundleContext bundleContext;

    @ServiceDependency(name = Foobar.PID, timeout = 60000)
    private Foobar foobar;

    @ServiceDependency
    private ConfigurationAdmin configurationAdmin;

    @ResourceDependency(filter = "(" + ResourceHandler.PATH + "=/META-INF/"
        + Foobar.PID + ".cfg)")
    private URL url;

    @Init
    Map<String, ?> init() throws IOException
    {
        Bundle bundle = this.bundleContext.getBundle();
        String location = bundle.getLocation();
        Configuration configuration = 
this.configurationAdmin.getConfiguration(Foobar.PID, location);
        Properties properties = new Properties();
        try (InputStream inputStream = this.url.openStream();)
        {
            properties.load(inputStream);
        }
        configuration.update((Dictionary) properties);
        return Collections.singletonMap(Foobar.PID + ".required", 
Boolean.FALSE);
    }

    @Test
    public void test() {
        this.foobar.blockUntilConfigured();
    }

}
{code}

The verification that JUnit test case is ready for evaluation 
(required->available) is done inside evaluate method. I got it working by 
patching {{TemporalServiceDependencyImpl}} just by removing the setRequired() 
method that would have been executed->failed when {{init()}} method of the test 
returned. 

ComponentProvider is still proprietary piece of code that functions in place of 
runtime bundle (reading annotations directly from the classes themselves), but 
I guess it's clear what it does. 

{code}
public abstract class DependencyManagerTestRule implements TestRule
{

    private DependencyManager dependencyManager;

    public DependencyManagerTestRule()
    {
        Object wrapped = this.getWrapped();
        Bundle bundle = FrameworkUtil.getBundle(wrapped.getClass());
        BundleContext bundleContext = bundle.getBundleContext();
        this.dependencyManager = new DependencyManager(bundleContext);
    }

    protected abstract Object getWrapped();

    @Override
    public Statement apply(final Statement wrapped, Description description)
    {
        Class<? extends Object> testClass = description.getTestClass();
        ComponentProvider componentProvider = new ComponentProvider(
            this.dependencyManager, testClass);
        final Component component = componentProvider.get();
        Object implementation = this.getWrapped();
        component.setImplementation(implementation);
        Statement statement = new Statement()
        {

            @Override
            public void evaluate() throws Throwable
            {
                DependencyManagerTestRule.this.dependencyManager.add(component);
                @SuppressWarnings("unchecked")
                List<Dependency> dependencies = component.getDependencies();
                for (Dependency dependency : dependencies)
                {
                    boolean required = dependency.isRequired();
                    boolean available = dependency.isAvailable();
                    if (!available && required)
                    {
                        ComponentDependencyDeclaration 
componentDependencyDeclaration = (ComponentDependencyDeclaration) dependency;
                        String type = componentDependencyDeclaration.getType();
                        String name = componentDependencyDeclaration.getName();
                        throw new IllegalStateException(type + ": " + name);
                    }
                }
                try
                {
                    wrapped.evaluate();
                }
                finally
                {
                    
DependencyManagerTestRule.this.dependencyManager.remove(component);
                }

            }

        };
        return statement;
    }

}
{code}

> Make TemporalServiceDependency always available (and/or allow it to be 
> optional)
> --------------------------------------------------------------------------------
>
>                 Key: FELIX-4847
>                 URL: https://issues.apache.org/jira/browse/FELIX-4847
>             Project: Felix
>          Issue Type: Wish
>          Components: Dependency Manager
>    Affects Versions: dependencymanager-3.2.0
>            Reporter: Tuomas Kiviaho
>
> I wanted to use temporal service to wait for CM update thread to finish what 
> it's doing (because the spec doesn't have a non-parallel version). 
> Everything worked fine until JUnit test rule said that the component isn't 
> ready yet. I was merely checking that every required dependency was also 
> available and to my surprise the temporal service was marked unavailable 
> until the CM had completed what it was doing.
> 1) Shouldn't temporal service be always available externally via available 
> property and keep track on the actual state only internally? This approach 
> might not be backwards compatible.
> 2) Could temporal service be allowed to be marked as optional. This would 
> suit my use case, but it feels like a 'golden hammer' approach because it 
> alters component's state machine behavior a bit which in turn can be harmful 
> for other use cases. 
> As a workaround I'd have to differentiate the dependencies somehow from each 
> other, but I see that the 4.x has removed the dedicated interface that I was 
> thinking of relying upon to. 



--
This message was sent by Atlassian JIRA
(v6.3.4#6332)

Reply via email to