Injecting one service into another 'ignored path' service

classic Classic list List threaded Threaded
20 messages Options
Reply | Threaded
Open this post in threaded view
|

Injecting one service into another 'ignored path' service

Christopher
Hi team,

I've implemented a WebSocket server endpoint inside a Tapestry application
by placing its class inside the 'services' folder, and adding its path to
'contributeIgnoredPathsFilter()' as below.

    public static void contributeIgnoredPathsFilter(Configuration<String>
configuration) {
        configuration.add("/websocket/.*");
    }

Tomcat successfully connects client apps calling this web socket URI.
Having the server endpoint as a Tapestry service ought to work well,
allowing me to PUSH messages out to connected clients.  So far so good.
The slight hiccup is that this server endpoint (service) depends on
another service, which I've tried injecting into a field of the endpoint
class:

    @InjectService("VesselStateTracker")
    private VesselStateTracker vesselStateTracker;

The problem is that when Tomcat invokes this endpoint class, the injected
service is not instantiated (makes sense, as Tapestry is configured to
ignore calls to the endpoint).  So a null pointer exception occurs as soon
as the endpoint tries to use 'vesselStateTracker' internally.

In AppModule I've tried eagerly loading both services after binding them,
and making the VesselStateTracker field a static field inside the
endpoint, but this wasn't successful.

Is there a simple solution or work-around to this almost expected problem?

Thanks & regards,

Chris.


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away." - Antoine de Saint-Exupéry.
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Christopher
I found a thread where someone else was grappling with this same issue,
but in his case the IoC framework was Google Guice.

http://tomcat.10.x6.nabble.com/ServerEndpoint-Guice-td5007388.html


Summarising his solution:


Create a custom WebSocket endpoint configurator class...

    public class WsEndpointConfigurator extends Configurator {

        @Override
        public <T> T getEndpointInstance(Class<T> endpointClass) throws
InstantiationException {
            return (T)
GuiceServletConfigFactory.getInjector().getInstance(endpointClass);
        }
    }


Then add this configurator to the ServerEndpoint annotation...

@ServerEndpoint(value = "/wsendpoint", configurator =
WsEndpointConfigurator.class)


So the WsEndpointConfigurator accesses the injector and uses it to
instantiate the ServerEndpoint class.

Might the Tapestry 5 IoC framework allow something similar to Guice?

Regards,

Chris.


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away." - Antoine de Saint-Exupéry.
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Christopher
In reply to this post by Christopher
And here, another version of custom configurator, where Jersey is the CDI
implementation.


public class CdiAwareConfigurator extends ServerEndpointConfig.Configurator {

    public <T> T getEndpointInstance(Class<T> endpointClass) throws
InstantiationException {
        return CDI.current().select(endpointClass).get();
    }
}


Does the Tapestry 5 IoC/DI layer also support acquiring of an instance of
a ServerEndpoint service class?  This seems to be the approach of several
other frameworks.

Chris.


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away." - Antoine de Saint-Exupéry.
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Thiago H de Paula Figueiredo
In reply to this post by Christopher
On Sun, Apr 7, 2019 at 4:03 AM Christopher Dodunski <
[hidden email]> wrote:

> Hi team,
>

Hello!


> The slight hiccup is that this server endpoint (service) depends on
> another service, which I've tried injecting into a field of the endpoint
> class:
>
>     @InjectService("VesselStateTracker")
>

Why not just @Inject? Do you have more than one service implementing
VesselStateTracker?


>     private VesselStateTracker vesselStateTracker;
>
> The problem is that when Tomcat invokes this endpoint class, the injected
> service is not instantiated (makes sense, as Tapestry is configured to
> ignore calls to the endpoint). So a null pointer exception occurs as soon
> as the endpoint tries to use 'vesselStateTracker' internally.


I'm afraid you're wrong about why the problem is happening. Tapestry-IoC is
the one actually taking care of services and it knows nothing about ignored
paths, which are a Tapestry(-core, the webapp) thing. The problem is that
your endpoint doesn't seem to be a Tapestry-IoC service itself, so it
doesn't get any dependency injection done to it.


> In AppModule I've tried eagerly loading both services after binding them,
> and making the VesselStateTracker field a static field inside the
> endpoint, but this wasn't successful.
>
> Is there a simple solution or work-around to this almost expected problem?
>

I'd try, in your endpoint, to get the Tapestry-IoC registry object from the
ServletContext using the TapestryFilter.REGISTRY_CONTEXT_NAME
(org.apache.tapestry5.application-registry) key, then call
registry.getService(VesselStateTracker.class) on it.


>
> Thanks & regards,
>
> Chris.
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]
>
>

--
Thiago
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Thiago H de Paula Figueiredo
In reply to this post by Christopher
On Tue, Apr 9, 2019 at 3:56 AM Christopher Dodunski <
[hidden email]> wrote:

> public class CdiAwareConfigurator extends
> ServerEndpointConfig.Configurator {
>
>     public <T> T getEndpointInstance(Class<T> endpointClass) throws
> InstantiationException {
>         return CDI.current().select(endpointClass).get();
>     }
> }
>
>
> Does the Tapestry 5 IoC/DI layer also support acquiring of an instance of
> a ServerEndpoint service class?  This seems to be the approach of several
> other frameworks.
>

It seems to be the other frameworks don't support ServerEndpoint classes
directly. Instead, the examples are just picking the endpoint object from
the other IoC frameworks. If your VesselTrackerService instance to be used
by the WebSocket endpoint isn't going to be shared with Tapestry pages OR
it doesn't keep state, you can use the examples you posted here, but
adapting them to Tapestry-IoC to just instantiate the Registry itself and
get the service instance through registry.getService(). For Tapestry pages,
the Registry is started up by TapestryFilter. Otherwise, follow the
documentation here:
https://tapestry.apache.org/starting-the-ioc-registry.html. Example:

Registry registry = RegistryBuilder.buildAndStartupRegistry(OneModule.class,
EndpointModule.class, ...);
YourEndpoint endpoint = registry.getService(YourEndpoint.class); //
YourEndpoint uses VesselStateTracker



> Chris.
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]
>
>

--
Thiago
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Christopher
In reply to this post by Christopher
Thank you very much for responding Thiago.

You write: "I'm afraid you're wrong about why the problem is happening.
Tapestry-IoC is the one actually taking care of services and it knows
nothing about ignored paths, which are a Tapestry(-core, the webapp)
thing. The problem is that your endpoint doesn't seem to be a Tapestry-IoC
service itself, so it
doesn't get any dependency injection done to it."

Perhaps my understanding of the problem is wrong, so please feel free to
correct me.  My endpoint service is configured to be autobuilt by the
bind() method in AppModule in the same way as other services I've created,
so I'd be surprised if Tapestry-IoC isn't recognising it as such.  What I
suspect is happening is that Tomcat, on receiving a WebSocket connection
request, is instantiating my endpoint but OUTSIDE of the Tapestry
application's context, meaning not only that any injected services never
get realised (they remain null), but also any class (static) field values
don't get shared across all instances of the endpoint class (those within
the registry versus those not).

So my solution was to have Tomcat get endpoint instances from the
Tapestry-IoC registry rather than instantiate them itself.  Hence the idea
of a custom Configurator, where I override the getEndpointInstance()
method to return an endpoint instance from the registry.

Interested in your thoughts on this.

Kind regards,

Chris.


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away." - Antoine de Saint-Exupéry.
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Christopher
In reply to this post by Christopher
Hi Thiago,

I discovered a mailing-list post, from around 9 years ago, where you
advised someone to override the TapestryFilter.init(Registry registry)
method to save the registry to a static field.

Ideally, I'd like to invoke the static method of a standalone class to
access the registry and its hosted services, rather than employ the likes
of ApplicationGlobals or ObjectLocator, as per the custom Configurator
examples posted earlier.

My ServerEndpoint class is not stateless, as it contains the likes of
Multimaps of endpoints, necessary for broadcasting to multiple clients
etc.  So it's not just a matter of ensuring that injected services work,
but also that class (static) field values are shared across all instances
of the endpoint (configured in AppModule to be autobuilt with 'PERTHREAD'
scope).

My efforts are based on the assumption that the surest way of ensuring
that ALL instances of my ServerEndpoint class have working injected
services, and identical class (static) field values, is that these
endpoints all originate from the same IoC registry.

Tapestry services are easy.  ServerEndpoints are easy.  Marrying the two
is proving a little tricky.  :-)

Regards,

Chris.


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away." - Antoine de Saint-Exupéry.
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Thiago H de Paula Figueiredo
In reply to this post by Christopher
On Tue, Apr 9, 2019 at 10:07 PM Christopher Dodunski <
[hidden email]> wrote:

> Thank you very much for responding Thiago.
>

You're welcome!


> Perhaps my understanding of the problem is wrong, so please feel free to
> correct me.  My endpoint service is configured to be autobuilt by the
> bind() method in AppModule in the same way as other services I've created,
> so I'd be surprised if Tapestry-IoC isn't recognising it as such.


I believe you're wrong here. Tapestry-IoC is recognizing it as such. Your
endpoint class instance which isn't being taken from Tapestry-IoC.


> What Isuspect is happening is that Tomcat, on receiving a WebSocket
> connection
> request, is instantiating my endpoint but OUTSIDE of the Tapestry
> application's context, meaning not only that any injected services never
> get realised (they remain null),


That's correct. Being pedantic, I'd say that Tomcat isn't using the
endpoint instance created by Tapestry-IoC.


> but also any class (static) field values
> don't get shared across all instances of the endpoint class (those within
> the registry versus those not).
>

Static fields in Java are basically global variables, but actually aren't,
but I guess this isn't working for you because you have live class
reloading turned on (it's on by default) for your endpoint service. Live
class reloading works by creating new versions (i.e. java.lang.Class
instances), so you set the static fields of one version, but then another
was actually being used.


> So my solution was to have Tomcat get endpoint instances from the
> Tapestry-IoC registry rather than instantiate them itself.  Hence the idea
> of a custom Configurator, where I override the getEndpointInstance()
> method to return an endpoint instance from the registry.
>

That's exactly what I suggested in my last e-mail and that's the correct
way of doing it IMHO. :) I guess I didn't explain it very well. :(


>
> Interested in your thoughts on this.
>
> Kind regards,
>
> Chris.
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]
>
>

--
Thiago
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Thiago H de Paula Figueiredo
In reply to this post by Christopher
On Thu, Apr 11, 2019 at 7:54 AM Christopher Dodunski <
[hidden email]> wrote:

> Hi Thiago,
>

Hi!


> I discovered a mailing-list post, from around 9 years ago, where you
> advised someone to override the TapestryFilter.init(Registry registry)
> method to save the registry to a static field.
>

That's a last resort solution when you cannot access the ServletContext.


> Ideally, I'd like to invoke the static method of a standalone class to
> access the registry and its hosted services, rather than employ the likes
> of ApplicationGlobals or ObjectLocator, as per the custom Configurator
> examples posted earlier.
>

You don't need to use ApplicationGlobals nor ObjectLocator (which is
basically the interface that defines the Tapestry-IoC registry) in this
case.


> My ServerEndpoint class is not stateless, as it contains the likes of
> Multimaps of endpoints, necessary for broadcasting to multiple clients
> etc.  So it's not just a matter of ensuring that injected services work,
> but also that class (static) field values are shared across all instances
> of the endpoint (configured in AppModule to be autobuilt with 'PERTHREAD'
> scope).
>

Why do you need static field in your service? Couldn't you move them to a
singleton service and then use it in your endpoint service?


> My efforts are based on the assumption that the surest way of ensuring
> that ALL instances of my ServerEndpoint class have working injected
> services, and identical class (static) field values, is that these
> endpoints all originate from the same IoC registry.
>

Yes, it is.


> Tapestry services are easy.  ServerEndpoints are easy.  Marrying the two
> is proving a little tricky.  :-)
>

Didn't you already find the solution for this, as your other message said?


>
> Regards,
>
> Chris.
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]
>
>

--
Thiago
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Christopher
In reply to this post by Christopher
Hi Thiago,

No, not solved yet.  Was still exploring options, for instance overriding
TapestryFilter.init(Registry).  Sorry if I wasn't clear.

I'm about to create a ServletContextListener in my app, so I can get
access to the ServletContext, and consequently the Registry, on app
initialisation.

Thanks for the great tip on extracting static fields of the ServerEndpoint
to a separate singleton service for keeping state on connections!

I'll report back on results.  And, of course, I'm always happy to
contribute any code that might prove useful to others.  Wait till I get it
working first.  :-)

Kind regards,

Chris.


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away." - Antoine de Saint-Exupéry.
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Christopher
In reply to this post by Christopher
By creating a ServletContextListener that sets a static ServletContext
variable in a RegistryProxy class I created, I now have access to the
Tapestry-IoC registry from anywhere in the application.  So the internal
wiring ought to be in place now.

However, when I use my custom endpoint Configurator to get an instance of
ServerEndpoint (implementation) from the registry, it seems that what I
get back is not castable:

15-Apr-2019 12:45:28.488 SEVERE [http-nio-8080-exec-915]
org.apache.coyote.AbstractProtocol$ConnectionHandler.process Error reading
request, ignored
 java.lang.ClassCastException: Cannot cast
$HarbourServerEndpoint_39c9cc24eb8b2a to
com.optomus.harbour.services.HarbourServerEndpointImpl
        at java.lang.Class.cast(Class.java:3369)
        at
com.optomus.harbour.services.HarbourServerEndpointConfigurator.getEndpointInstance(HarbourServerEndpointConfigurator.java:17)
        at
org.apache.tomcat.websocket.pojo.PojoEndpointServer.onOpen(PojoEndpointServer.java:44)
        at
org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.init(WsHttpUpgradeHandler.java:133)
        at
org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:846)
        at
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1471)
        at
org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:748)

Just wondering whether you have any idea why this might be?  Accessing the
registry is via a chain of 'generic' methods, but I can't find fault here.
 Given that Tapestry works similarly when actioning the @Inject
annotation, you might immediately recognise where the fault lies.

My Configurator method:

    @Override
    public <T> T getEndpointInstance(Class<T> endpointClass) throws
InstantiationException {
        return
endpointClass.cast(RegistryProxy.getService(HarbourServerEndpoint.class));
    }

My RegistryProxy method:

    public static <T> T getService(Class<T> serviceInterface){
        Registry registry =
(Registry)context.getAttribute(TapestryFilter.REGISTRY_CONTEXT_NAME);
        return registry.getService(serviceInterface);
    }

Incidentally, altering my Configurator method to use old style casting (as
per below) didn't solve the problem.

    @Override
    public <T> T getEndpointInstance(Class<T> endpointClass) throws
InstantiationException {
        return (T)RegistryProxy.getService(HarbourServerEndpoint.class);
    }

Finally, with no casting at all, the compiler gives the error:

    Error:(17, 40) java: incompatible types: inference variable T has
incompatible bounds
        equality constraints:
com.optomus.harbour.services.HarbourServerEndpoint
        upper bounds: T,java.lang.Object

Regards,

Chris.


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away." - Antoine de Saint-Exupéry.
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Christopher
In reply to this post by Christopher
By the way, the @ServerEndpoint and @OnOpen and other WebSocket
annotations are on the implementation class, not the interface.  Perhaps
this is not right when providing endpoints as a Tapestry-IoC service?


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away." - Antoine de Saint-Exupéry.
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Thiago H de Paula Figueiredo
In reply to this post by Christopher
On Mon, Apr 15, 2019 at 8:04 PM Christopher Dodunski <
[hidden email]> wrote:

> By creating a ServletContextListener that sets a static ServletContext
> variable in a RegistryProxy class I created, I now have access to the
> Tapestry-IoC registry from anywhere in the application.


Nice!


> However, when I use my custom endpoint Configurator to get an instance of
> ServerEndpoint (implementation) from the registry, it seems that what I
> get back is not castable:
>  java.lang.ClassCastException: Cannot cast
> $HarbourServerEndpoint_39c9cc24eb8b2a to
> com.optomus.harbour.services.HarbourServerEndpointImpl
>

It's not castable nor it should. You shouldn't cast to the service
implementation class. Just use the service interface instead. I'd try this:

    @Override
    public <T> T getEndpointInstance(Class<T> endpointClass) throws
InstantiationException {
        return RegistryProxy.getService(endpointClass.class);
    }

Why is it even referencing HarbourServerEndpointImpl? This, the service
implementation class, should only be referenced in AppModule (or other
Tapestry-IoC module) when declaring the service and maybe also when making
unit tests. Everything else should use HarbourServerEndpoint instead.


My Configurator method:

>
>     @Override
>     public <T> T getEndpointInstance(Class<T> endpointClass) throws
> InstantiationException {
>         return
> endpointClass.cast(RegistryProxy.getService(HarbourServerEndpoint.class));
>     }
>
> My RegistryProxy method:
>
>     public static <T> T getService(Class<T> serviceInterface){
>         Registry registry =
> (Registry)context.getAttribute(TapestryFilter.REGISTRY_CONTEXT_NAME);
>         return registry.getService(serviceInterface);
>     }
>
> Incidentally, altering my Configurator method to use old style casting (as
> per below) didn't solve the problem.
>
>     @Override
>     public <T> T getEndpointInstance(Class<T> endpointClass) throws
> InstantiationException {
>         return (T)RegistryProxy.getService(HarbourServerEndpoint.class);
>     }
>
> Finally, with no casting at all, the compiler gives the error:
>
>     Error:(17, 40) java: incompatible types: inference variable T has
> incompatible bounds
>         equality constraints:
> com.optomus.harbour.services.HarbourServerEndpoint
>         upper bounds: T,java.lang.Object
>
> Regards,
>
> Chris.
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]
>
>

--
Thiago
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Thiago H de Paula Figueiredo
In reply to this post by Christopher
On Mon, Apr 15, 2019 at 8:29 PM Christopher Dodunski <
[hidden email]> wrote:

> By the way, the @ServerEndpoint and @OnOpen and other WebSocket
> annotations are on the implementation class, not the interface.  Perhaps
> this is not right when providing endpoints as a Tapestry-IoC service?
>

Since Tapestry 5.4, annotations on service implementation methods are
copied to the service proxy, so you can keep the annotations on the service
class if the endpoint library. If it doesn't work, which is most probably
due to the way the endpoint library works, you should move the annotations
to the interface.


>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]
>
>

--
Thiago
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Christopher
In reply to this post by Christopher
Hi Thiago,

>> Why is it even referencing HarbourServerEndpointImpl? This, the service
>> implementation class, should only be referenced in AppModule (or other
>> Tapestry-IoC module) when declaring the service and maybe also when making
>> unit tests. Everything else should use HarbourServerEndpoint instead.

It seems that, when Tomcat invokes the "<T> T getEndpointInstance(Class<T>
endpointClass)" method, Class<T> type is the class decorated with the
@ServerEndpoint annotation - HarbourServerEndpointImpl.class in my case.
Consequently, this method must return an instance of this class.  And a
service  proxy object - even though it implements the same interface -
ultimately is another type.

To date, my only option has been to define the endpoint service as a
concrete implementation class, without binding it to any interface.  This
means that Tomcat gets an instance of the class annotated with
@ServerEndpoint, just as it expects.

Tapestry still injects into this concrete implementation class its needed
dependencies (eg. VesselStateTracker), which is a saving grace.  But doing
away with the interface comes at a cost.  Quoting from Igor Drobiazko's
book on Tapestry 5:

"It's highly discouraged to define services as concrete classes. Such
services will not have a proxy and so will lose some very important
characteristics. For example, you will not be able to define a scope for
the particular service. You also will lose the possibility to advise and
decorate your service."

If anyone has a better solution, I'd love to hear it.  :-)

Regards,

Chris.


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away." - Antoine de Saint-Exupéry.
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Thiago H de Paula Figueiredo
So I think you basically have two options:

1) Move the annotations to the service interface. This way, the endpoint
library would call your code with the service interface and you wouldn't
have casting issues.
2) Not defining the service as an interface, but as a concrete class.
Instead of binder.bind(HarbourServerEndpoint.class,
HarbourServerEndpointImpl.class), just
binder.bind(HarbourServerEndpointImpl.class).

I'd go with #1.

On Fri, Apr 19, 2019 at 8:37 AM Christopher Dodunski <
[hidden email]> wrote:

> Hi Thiago,
>
> >> Why is it even referencing HarbourServerEndpointImpl? This, the service
> >> implementation class, should only be referenced in AppModule (or other
> >> Tapestry-IoC module) when declaring the service and maybe also when
> making
> >> unit tests. Everything else should use HarbourServerEndpoint instead.
>
> It seems that, when Tomcat invokes the "<T> T getEndpointInstance(Class<T>
> endpointClass)" method, Class<T> type is the class decorated with the
> @ServerEndpoint annotation - HarbourServerEndpointImpl.class in my case.
> Consequently, this method must return an instance of this class.  And a
> service  proxy object - even though it implements the same interface -
> ultimately is another type.
>
> To date, my only option has been to define the endpoint service as a
> concrete implementation class, without binding it to any interface.  This
> means that Tomcat gets an instance of the class annotated with
> @ServerEndpoint, just as it expects.
>
> Tapestry still injects into this concrete implementation class its needed
> dependencies (eg. VesselStateTracker), which is a saving grace.  But doing
> away with the interface comes at a cost.  Quoting from Igor Drobiazko's
> book on Tapestry 5:
>
> "It's highly discouraged to define services as concrete classes. Such
> services will not have a proxy and so will lose some very important
> characteristics. For example, you will not be able to define a scope for
> the particular service. You also will lose the possibility to advise and
> decorate your service."
>
> If anyone has a better solution, I'd love to hear it.  :-)
>
> Regards,
>
> Chris.
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]
>
>

--
Thiago
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Christopher
In reply to this post by Christopher
If option #1 works, it could be the ideal solution.  I've only ever seen
WebSocket annotations on a POJO, never an interface.  Not to say it can't
be done.

I was about to attempt an 'option #3': do away with the custom
Configurator (allowing Tomcat to instantiate endpoints), with endpoints
getting their dependencies via the RegistryProxy I created, rather than
having them injected.

I'll try option #1 first.  :)

Are you seeking new Tapestry committers?  Once I have a final working
solution, I'm happy to document the Tapestry and WebSocket integration for
others, including source code (ServletContextListener, RegistryProxy
etc.).

Regards,

Chris.


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away." - Antoine de Saint-Exupéry.
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Christopher
In reply to this post by Christopher
Update: it seems that option #1 is off the table.

From the Java WebSockets specification:

4.1
@ServerEndpoint
"This class level annotation signifies that the Java class it decorates
must be deployed by the implementation as a websocket server endpoint and
made available in the URI-space of the websocket implementation. The class
must be public, CONCRETE, and have a public no-args constructor."  :(

Option #2 - opting for concrete service class alone - would mean losing
the ability to set a scope of 'per thread', yes?

I may have to discard the WebSocket annotations and create the server
endpoint programmatically in that case.

Regards,

Chris.


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away." - Antoine de Saint-Exupéry.
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Thiago H de Paula Figueiredo
On Wed, Apr 24, 2019 at 7:03 AM Christopher Dodunski <
[hidden email]> wrote:

> From the Java WebSockets specification:
>
> 4.1
> @ServerEndpoint
> "This class level annotation signifies that the Java class it decorates
> must be deployed by the implementation as a websocket server endpoint and
> made available in the URI-space of the websocket implementation. The class
> must be public, CONCRETE, and have a public no-args constructor."  :(
>

Good catch!


> Option #2 - opting for concrete service class alone - would mean losing
> the ability to set a scope of 'per thread', yes?
>

I don't think so, but I'm not sure it would work for a server endpoint, as
the endpoint instance is probably only requested to the configurator once.

I may have to discard the WebSocket annotations and create the server
> endpoint programmatically in that case.
>

Indeed. I'd create an endpoint class which does nothing but delegate method
calls to injected service(s).


>
> Regards,
>
> Chris.
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]
>
>

--
Thiago
Reply | Threaded
Open this post in threaded view
|

Re: Injecting one service into another 'ignored path' service

Christopher
In reply to this post by Christopher
Hi Thiago,

Indeed, if I want to provide endpoints as a Tapestry-IoC service,
singleton may be the only option.  Running as a 'per thread' service,
connections are successfully established, but subsequent method calls on
the connection by Tomcat - like to @OnError and/or @OnClose - results in
the registry returning a new instance (not desirable).  So seems I will
have to do like you suggested, which means no longer being able to persist
connection endpoints to a static map with a simple
'connections.put(this)'.

The good news is that Tomcat now successfully fetches endpoint instance(s)
from the Tapestry-IoC registry, and in the end without even needing a
custom Configurator.  :-)

Quoting from the Oracle documentation on WebSocket endpoints:

"When deployed as a server endpoint, the implementation uses the
ServerEndpointConfig.Configurator.getEndpointInstance(java.lang.Class<T>)
method to obtain the endpoint instance it will use for each new client
connection. If the developer uses the default
ServerEndpointConfig.Configurator, there will be precisely one endpoint
instance per active client connection. Consequently, in this typical case,
when implementing/overriding the methods of Endpoint, the developer is
guaranteed that there will be at most one thread calling each endpoint
instance at a time.

If the developer provides a custom ServerEndpointConfig.Configurator which
overrides the default policy for endpoint instance creation, for example,
using a single Endpoint instance for multiple client connections, the
developer may need to write code that can execute concurrently."

Regards,

Chris.


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away." - Antoine de Saint-Exupéry.