You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@tapestry.apache.org by "david.boyce" <da...@thisisnumero.com> on 2009/09/18 11:11:28 UTC

passing a service into a component as a parameter

I am trying to pass services into my components via parameters, i.e. I want
something like:

<div t:type="doofer" t:controller="service:dooferController"/>

Where service is some custom binding type and dooferController is a service
I've defined in my application module.

The problem is that the only way I know of getting hold of a service is via
the Registry, which requires a type (as opposed to a spring application
context) and in my BindingFactory, I don't have access to the type of the
parameter, so exactly how to retrieve the service is unclear. 

At the moment, I've implemented something that works, but via accessing the
RegistryWrapper's underlying Registries allServiceDefs collection, which
seems a bit fragile and not ideal for non-singleton services. 

Anybody got any ideas?

Cheers

David
-- 
View this message in context: http://www.nabble.com/passing-a-service-into-a-component-as-a-parameter-tp25505462p25505462.html
Sent from the Tapestry - User mailing list archive at Nabble.com.


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: passing a service into a component as a parameter

Posted by "david.boyce" <da...@thisisnumero.com>.
Thanks that's really useful and does exactly what we want, it's not a problem
for us to make all our controller interfaces extend a marker type. 

Cheers

Dave


joshcanfield wrote:
> 
> I'm assuming that your users aren't uploading the components or the
> services, just the pages?
> 
> If you use a common interface for your services you can use that class
> to access the registry.
> 
> I built a quick binding factory and wrote a couple services for it to
> use here is the implementation:
> 
> public class ServiceBindingFactory implements BindingFactory {
>    private BindingSource _source;
> 
>    private ObjectLocator _locator;
> 
>    ServiceBindingFactory(final BindingSource source, ObjectLocator
> locator) {
>        _source = source;
>        _locator = locator;
>    }
> 
>    public Binding newBinding(String description, ComponentResources
> container, ComponentResources component,
>                              String expression, Location location) {
> 
>        Object service = null;
>        try {
> // All services that use this binding must implement the PublicService
> Interface
> // this also allows you to keep your private services private.
>            service = _locator.getService(expression, PublicService.class);
>        } catch (RuntimeException e ) {
>            // just ignore it we'll handle null later.
>        }
> 
>        return new ServiceBinding(service, expression, location);
>    }
> 
>    public class ServiceBinding extends AbstractBinding {
>        private Location _location;
>        private Object _service;
>        private String _expression;
> 
>        ServiceBinding(Object service, String expression, Location
> location) {
>            _service = service;
>            _expression = expression;
>            _location = location;
>        }
> 
>        public Object get() {
>            if (_service == null) {
>                throw new TapestryException("Unable to locate service:
> " + _expression
>                        , _location, null);
>            }
>            return _service;
>        }
>    }
> }
> 
> 
> The binder and services are added to the AppModule like this:
> 
> public class AppModule {
>    public static void bind(ServiceBinder binder) {
>        // Bind the PublicService classes available to unsafe templates
> via the "service" binding.
>        binder.bind(ColorService.class,
> RedColorService.class).withId("redcolorservice");
>        binder.bind(ColorService.class,
> BlueColorService.class).withId("bluecolorservice");
> 
>        binder.bind(FontService.class,
> ArialFontService.class).withId("arialfontservice");
>        binder.bind(FontService.class,
> MonospaceFontService.class).withId("monospacefontservice");
>    }
> 
>    public static void contributeBindingSource(
>            MappedConfiguration<String, BindingFactory> configuration,
>            BindingSource bindingSource,
>            ObjectLocator objectLocator
>    ) {
>        configuration.add("service", new
> ServiceBindingFactory(bindingSource,objectLocator));
>    }
> }
> 
> Your component might look like this:
> 
> public class StyleAdder {
> 
>    @Parameter
>    private ColorService _color;
>    @Parameter
>    private FontService _font;
> 
>    void beforeRenderTemplate(MarkupWriter writer) {
>        writer.element("div");
>        StringBuilder style = new StringBuilder();
>        if (_color != null)
>            style.append("color:").append(_color.getColor()).append(';');
> 
>        if (_font != null) {
>          
>  style.append("font-family:").append(_font.getFont()).append(';');
>        }
>        if ( style.length() > 0 )
>            writer.attributes("style", style);
>    }
> 
>    void afterRenderTemplate(MarkupWriter writer) {
>        writer.end();
>    }
> }
> 
> And the page would include the component like this:
> 
> <t:StyleAdder t:font="service:monospacefontservice"
> t:color="service:bluecolorservice">Blue Text</t:StyleAdder>
> <t:StyleAdder t:color="service:redcolorservice">Red Text</t:StyleAdder>
> <t:StyleAdder t:font="service:arialfontservice"
> t:color="service:redcolorservice">Red Arial Text</t:StyleAdder>
> 
> 
> Josh
> 
> On Mon, Sep 21, 2009 at 6:28 AM, david.boyce
> <da...@thisisnumero.com> wrote:
>>
>>
>> What does the text "dooferController" represent in this context? Is it
>>> a string that represents the name of the service interface that you
>>> want to load? or is it a property in your page that holds a reference
>>> to the service implementation?
>>>
>>
>> Sorry, my question probably wasn't very clear. Basically, the components
>> we
>> are creating are intended to be reusable, and their behaviour is largely
>> defined by services they delegate to, as per your graphing example with
>> the
>> BarChartService, PieChartService and ScatterPlotService.
>>
>> In our product most of the tml is actually supplied by the user, and
>> there
>> is no backing code for any of this tml in the conventional sense. When a
>> component is used, it's quite convenient to specify the implementation of
>> its backing service as a parameter, however, the only way I can see of
>> doing
>> this is to introduce a new binding type, which in my example I've called
>> "service". The implementation of the sevice binding would ideally pull
>> the
>> service out of the Registry.
>>
>> The problem is when you get into the BindingFactory implementation:
>>
>> @Inject Registry registry;
>>
>> @Override
>> public Binding newBinding(
>>                        final String description,
>>                        final ComponentResources container,
>>                        final ComponentResources component,
>>                        String expression,
>>                        final Location location) {
>>
>>   // oops, how do i get the type?
>>   registry.getService(expression, meh?);
>> }
>>
>> So I appreciate that you could probably pass the service id and the
>> service
>> type in the expression somehow, but that seems weak to me and wouldn't go
>> down well with our users.
>>
>> As it happens, we had been passing the services into the components in a
>> different way previously, as we couldn't see a good way of achieving it
>> via
>> parameters (for the previously stated reasons), but came back to it
>> recently. After posting the previous message on this forum, I had the
>> idea
>> of doing the following, using a bespoke binding type I had already
>> created
>> called factory.
>>
>> The gist of this approach is to have 2 parameters, an internal parameter
>> for
>> the actual service, where you specifiy both the service id and the
>> parameter
>> type, and a public parameter for the service id (which gets fed into the
>> first parameter).
>>
>> // the user specifies this...
>> @Property @Parameter(required=true, defaultPrefix="literal")
>> private String controller;
>>
>> // this is a bespoke binding type, the user doesn't modify this parameter
>> @Parameter(defaultPrefix="factory", value="tapestry(" +
>>        "id -> prop:controller," +
>>        "serviceType -> prop:controllerType )")
>> private DataFormRendererController controllerService;
>>
>> Which basically achieves what we want, but still doesn't seem very good -
>> we
>> have to repeat this pattern for every component we have and require 2
>> parameters per service.
>>
>> It just seems to me that being able to inject services out of the
>> tapestry
>> registry into the components via parameters without having to perform any
>> special incantations would be a nice feature... but meh, what do I know
>> (apparently nothing).
>>
>> Cheers,
>>
>> Dave
>>
>>
>>
>>
>>
>>
>> --
>> View this message in context:
>> http://www.nabble.com/passing-a-service-into-a-component-as-a-parameter-tp25505462p25530329.html
>> Sent from the Tapestry - User mailing list archive at Nabble.com.
>>
>>
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
>> For additional commands, e-mail: users-help@tapestry.apache.org
>>
>>
> 
> 
> 
> --
> --
> TheDailyTube.com. Sign up and get the best new videos on the internet
> delivered fresh to your inbox.
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> For additional commands, e-mail: users-help@tapestry.apache.org
> 
> 
> 

-- 
View this message in context: http://www.nabble.com/passing-a-service-into-a-component-as-a-parameter-tp25505462p25566444.html
Sent from the Tapestry - User mailing list archive at Nabble.com.


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: passing a service into a component as a parameter

Posted by Josh Canfield <jo...@thedailytube.com>.
I'm assuming that your users aren't uploading the components or the
services, just the pages?

If you use a common interface for your services you can use that class
to access the registry.

I built a quick binding factory and wrote a couple services for it to
use here is the implementation:

public class ServiceBindingFactory implements BindingFactory {
   private BindingSource _source;

   private ObjectLocator _locator;

   ServiceBindingFactory(final BindingSource source, ObjectLocator locator) {
       _source = source;
       _locator = locator;
   }

   public Binding newBinding(String description, ComponentResources
container, ComponentResources component,
                             String expression, Location location) {

       Object service = null;
       try {
// All services that use this binding must implement the PublicService Interface
// this also allows you to keep your private services private.
           service = _locator.getService(expression, PublicService.class);
       } catch (RuntimeException e ) {
           // just ignore it we'll handle null later.
       }

       return new ServiceBinding(service, expression, location);
   }

   public class ServiceBinding extends AbstractBinding {
       private Location _location;
       private Object _service;
       private String _expression;

       ServiceBinding(Object service, String expression, Location location) {
           _service = service;
           _expression = expression;
           _location = location;
       }

       public Object get() {
           if (_service == null) {
               throw new TapestryException("Unable to locate service:
" + _expression
                       , _location, null);
           }
           return _service;
       }
   }
}


The binder and services are added to the AppModule like this:

public class AppModule {
   public static void bind(ServiceBinder binder) {
       // Bind the PublicService classes available to unsafe templates
via the "service" binding.
       binder.bind(ColorService.class,
RedColorService.class).withId("redcolorservice");
       binder.bind(ColorService.class,
BlueColorService.class).withId("bluecolorservice");

       binder.bind(FontService.class,
ArialFontService.class).withId("arialfontservice");
       binder.bind(FontService.class,
MonospaceFontService.class).withId("monospacefontservice");
   }

   public static void contributeBindingSource(
           MappedConfiguration<String, BindingFactory> configuration,
           BindingSource bindingSource,
           ObjectLocator objectLocator
   ) {
       configuration.add("service", new
ServiceBindingFactory(bindingSource,objectLocator));
   }
}

Your component might look like this:

public class StyleAdder {

   @Parameter
   private ColorService _color;
   @Parameter
   private FontService _font;

   void beforeRenderTemplate(MarkupWriter writer) {
       writer.element("div");
       StringBuilder style = new StringBuilder();
       if (_color != null)
           style.append("color:").append(_color.getColor()).append(';');

       if (_font != null) {
           style.append("font-family:").append(_font.getFont()).append(';');
       }
       if ( style.length() > 0 )
           writer.attributes("style", style);
   }

   void afterRenderTemplate(MarkupWriter writer) {
       writer.end();
   }
}

And the page would include the component like this:

<t:StyleAdder t:font="service:monospacefontservice"
t:color="service:bluecolorservice">Blue Text</t:StyleAdder>
<t:StyleAdder t:color="service:redcolorservice">Red Text</t:StyleAdder>
<t:StyleAdder t:font="service:arialfontservice"
t:color="service:redcolorservice">Red Arial Text</t:StyleAdder>


Josh

On Mon, Sep 21, 2009 at 6:28 AM, david.boyce
<da...@thisisnumero.com> wrote:
>
>
> What does the text "dooferController" represent in this context? Is it
>> a string that represents the name of the service interface that you
>> want to load? or is it a property in your page that holds a reference
>> to the service implementation?
>>
>
> Sorry, my question probably wasn't very clear. Basically, the components we
> are creating are intended to be reusable, and their behaviour is largely
> defined by services they delegate to, as per your graphing example with the
> BarChartService, PieChartService and ScatterPlotService.
>
> In our product most of the tml is actually supplied by the user, and there
> is no backing code for any of this tml in the conventional sense. When a
> component is used, it's quite convenient to specify the implementation of
> its backing service as a parameter, however, the only way I can see of doing
> this is to introduce a new binding type, which in my example I've called
> "service". The implementation of the sevice binding would ideally pull the
> service out of the Registry.
>
> The problem is when you get into the BindingFactory implementation:
>
> @Inject Registry registry;
>
> @Override
> public Binding newBinding(
>                        final String description,
>                        final ComponentResources container,
>                        final ComponentResources component,
>                        String expression,
>                        final Location location) {
>
>   // oops, how do i get the type?
>   registry.getService(expression, meh?);
> }
>
> So I appreciate that you could probably pass the service id and the service
> type in the expression somehow, but that seems weak to me and wouldn't go
> down well with our users.
>
> As it happens, we had been passing the services into the components in a
> different way previously, as we couldn't see a good way of achieving it via
> parameters (for the previously stated reasons), but came back to it
> recently. After posting the previous message on this forum, I had the idea
> of doing the following, using a bespoke binding type I had already created
> called factory.
>
> The gist of this approach is to have 2 parameters, an internal parameter for
> the actual service, where you specifiy both the service id and the parameter
> type, and a public parameter for the service id (which gets fed into the
> first parameter).
>
> // the user specifies this...
> @Property @Parameter(required=true, defaultPrefix="literal")
> private String controller;
>
> // this is a bespoke binding type, the user doesn't modify this parameter
> @Parameter(defaultPrefix="factory", value="tapestry(" +
>        "id -> prop:controller," +
>        "serviceType -> prop:controllerType )")
> private DataFormRendererController controllerService;
>
> Which basically achieves what we want, but still doesn't seem very good - we
> have to repeat this pattern for every component we have and require 2
> parameters per service.
>
> It just seems to me that being able to inject services out of the tapestry
> registry into the components via parameters without having to perform any
> special incantations would be a nice feature... but meh, what do I know
> (apparently nothing).
>
> Cheers,
>
> Dave
>
>
>
>
>
>
> --
> View this message in context: http://www.nabble.com/passing-a-service-into-a-component-as-a-parameter-tp25505462p25530329.html
> Sent from the Tapestry - User mailing list archive at Nabble.com.
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> For additional commands, e-mail: users-help@tapestry.apache.org
>
>



--
--
TheDailyTube.com. Sign up and get the best new videos on the internet
delivered fresh to your inbox.

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: passing a service into a component as a parameter

Posted by "david.boyce" <da...@thisisnumero.com>.
Thiago,

I will raise a jira for this, but joshcanfield has supplied a workaround
that works really well, so this isn't an issue for us any more. 

Dave


Thiago H. de Paula Figueiredo wrote:
> 
> Em Mon, 21 Sep 2009 10:28:48 -0300, david.boyce  
> <da...@thisisnumero.com> escreveu:
> 
>> It just seems to me that being able to inject services out of the  
>> tapestry registry into the components via parameters without having to  
>> perform any
>> special incantations would be a nice feature... but meh, what do I know
>> (apparently nothing).
> 
> Please file a JIRA for it in tapestry-ioc (as its implementation will need  
> to add one method to ObjectLocator or create an ObjectLocator2 interface).
> 
> -- 
> Thiago H. de Paula Figueiredo
> Independent Java consultant, developer, and instructor
> http://www.arsmachina.com.br/thiago
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> For additional commands, e-mail: users-help@tapestry.apache.org
> 
> 
> 

-- 
View this message in context: http://www.nabble.com/passing-a-service-into-a-component-as-a-parameter-tp25505462p25566748.html
Sent from the Tapestry - User mailing list archive at Nabble.com.


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: passing a service into a component as a parameter

Posted by "Thiago H. de Paula Figueiredo" <th...@gmail.com>.
Em Mon, 21 Sep 2009 10:28:48 -0300, david.boyce  
<da...@thisisnumero.com> escreveu:

> It just seems to me that being able to inject services out of the  
> tapestry registry into the components via parameters without having to  
> perform any
> special incantations would be a nice feature... but meh, what do I know
> (apparently nothing).

Please file a JIRA for it in tapestry-ioc (as its implementation will need  
to add one method to ObjectLocator or create an ObjectLocator2 interface).

-- 
Thiago H. de Paula Figueiredo
Independent Java consultant, developer, and instructor
http://www.arsmachina.com.br/thiago

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: passing a service into a component as a parameter

Posted by "david.boyce" <da...@thisisnumero.com>.

What does the text "dooferController" represent in this context? Is it
> a string that represents the name of the service interface that you
> want to load? or is it a property in your page that holds a reference
> to the service implementation?
> 

Sorry, my question probably wasn't very clear. Basically, the components we
are creating are intended to be reusable, and their behaviour is largely
defined by services they delegate to, as per your graphing example with the
BarChartService, PieChartService and ScatterPlotService. 

In our product most of the tml is actually supplied by the user, and there
is no backing code for any of this tml in the conventional sense. When a
component is used, it's quite convenient to specify the implementation of
its backing service as a parameter, however, the only way I can see of doing
this is to introduce a new binding type, which in my example I've called
"service". The implementation of the sevice binding would ideally pull the
service out of the Registry.

The problem is when you get into the BindingFactory implementation: 

@Inject Registry registry;

@Override
public Binding newBinding(
			final String description, 
			final ComponentResources container,
			final ComponentResources component, 
			String expression, 
			final Location location) {	

   // oops, how do i get the type?
   registry.getService(expression, meh?);
}

So I appreciate that you could probably pass the service id and the service
type in the expression somehow, but that seems weak to me and wouldn't go
down well with our users.

As it happens, we had been passing the services into the components in a
different way previously, as we couldn't see a good way of achieving it via
parameters (for the previously stated reasons), but came back to it
recently. After posting the previous message on this forum, I had the idea
of doing the following, using a bespoke binding type I had already created
called factory. 

The gist of this approach is to have 2 parameters, an internal parameter for
the actual service, where you specifiy both the service id and the parameter
type, and a public parameter for the service id (which gets fed into the
first parameter).

// the user specifies this...
@Property @Parameter(required=true, defaultPrefix="literal")
private String controller;

// this is a bespoke binding type, the user doesn't modify this parameter
@Parameter(defaultPrefix="factory", value="tapestry(" +
	"id -> prop:controller," +
	"serviceType -> prop:controllerType )")			
private DataFormRendererController controllerService;

Which basically achieves what we want, but still doesn't seem very good - we
have to repeat this pattern for every component we have and require 2
parameters per service. 

It just seems to me that being able to inject services out of the tapestry
registry into the components via parameters without having to perform any
special incantations would be a nice feature... but meh, what do I know
(apparently nothing). 

Cheers,

Dave






-- 
View this message in context: http://www.nabble.com/passing-a-service-into-a-component-as-a-parameter-tp25505462p25530329.html
Sent from the Tapestry - User mailing list archive at Nabble.com.


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: passing a service into a component as a parameter

Posted by Josh Canfield <jo...@thedailytube.com>.
>
> I am trying to pass services into my components via parameters, i.e. I want
> something like:
>
> <div t:type="doofer" t:controller="service:dooferController"/>
>

What does the text "dooferController" represent in this context? Is it
a string that represents the name of the service interface that you
want to load? or is it a property in your page that holds a reference
to the service implementation?

Are you trying to avoid injecting the "dooferController" in the
page/parent component and simply passing it without the special
binding?

If for instance you were building a graphing app and had
BarChartService, PieChartService and ScatterPlotService and needed to
use the service in the page and some child components; I would have
their individual service interfaces implement a base ChartService
interface. With a ChartService property in the page holding the chosen
implementation you can pass them to the component using the standard
property bindings built into tapestry.

If you really want to build a binding for looking up services then try
using Class.forName() and passing the actual name of the service
interface you want to pull:

<div t:type="doofer" t:controller="service:DooferController"/>

since you know where your services live
(your.tapestry.package.services) you can append the passed name, or
pass a fully qualified path... or you could get really configurable
and add configuration with several paths to prepend.

Good luck,
Josh

On Fri, Sep 18, 2009 at 2:11 AM, david.boyce
<da...@thisisnumero.com> wrote:
>
> I am trying to pass services into my components via parameters, i.e. I want
> something like:
>
> <div t:type="doofer" t:controller="service:dooferController"/>
>
> Where service is some custom binding type and dooferController is a service
> I've defined in my application module.
>
> The problem is that the only way I know of getting hold of a service is via
> the Registry, which requires a type (as opposed to a spring application
> context) and in my BindingFactory, I don't have access to the type of the
> parameter, so exactly how to retrieve the service is unclear.
>
> At the moment, I've implemented something that works, but via accessing the
> RegistryWrapper's underlying Registries allServiceDefs collection, which
> seems a bit fragile and not ideal for non-singleton services.
>
> Anybody got any ideas?
>
> Cheers
>
> David
> --
> View this message in context: http://www.nabble.com/passing-a-service-into-a-component-as-a-parameter-tp25505462p25505462.html
> Sent from the Tapestry - User mailing list archive at Nabble.com.
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> For additional commands, e-mail: users-help@tapestry.apache.org
>
>



-- 
--
TheDailyTube.com. Sign up and get the best new videos on the internet
delivered fresh to your inbox.

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org