Hi Justin,
I have a solution for the unget problem - for a recent project I have
created a class OsgiServiceProxy (also used for the howm-grown injection
mechanism) that allows to use a service without having access to the
request (using java.lang.reflect.Proxy, see [1],[2]). The proxy
gets/ungets the service for every method call (not ideal from a
performance perspective, but it is surprisingly fast and we didn't have
a significant performance penalty here). It could probably be rewritten
to use a ServiceTracker (to be more efficient), but it works as is and
the beauty in the solution is, that you can use/store the service like a
service reference because the service proxy never holds on to the actual
service between method calls.
Regards
Georg
[1] Usage
MyService myService =
OsgiServiceProxy.create(abstractComponent.getClass(), MyService.class);
[2] Impl
public final class OsgiServiceProxy implements InvocationHandler {
private static final Logger LOG =
LoggerFactory.getLogger(OsgiServiceProxy.class);
/**
* Creates a proxy for a OSGI service.
*
* @param hostClass The class that uses the service
* @param osgiServiceClass the service class
* @return proxy to the service
*/
public static <T> T create(final Class<?> hostClass, final Class<T>
osgiServiceClass) {
OsgiServiceProxy osgiServiceProxy = new
OsgiServiceProxy(hostClass, osgiServiceClass);
Object service =
Proxy.newProxyInstance(hostClass.getClassLoader(), new Class[] {
osgiServiceClass }, osgiServiceProxy);
@SuppressWarnings("unchecked")
T retVal = (T) service;
return retVal;
}
private Class<?> hostClass;
private Class<?> osgiServiceClass;
private OsgiServiceProxy(final Class<?> hostClass, final Class<?>
osgiServiceClass) {
this.hostClass = hostClass;
this.osgiServiceClass = osgiServiceClass;
}
@Override
public Object invoke(final Object proxyObj, final Method method,
final Object[] methodArgs) throws Throwable {
LOG.debug("OsgiServiceProxy: Invocation on service {} method
{}", this.osgiServiceClass, method);
BundleContext componentTypeBundleContext =
FrameworkUtil.getBundle(this.hostClass).getBundleContext();
ServiceReference serviceReference =
componentTypeBundleContext.getServiceReference(this.osgiServiceClass.getName());
if (serviceReference == null) {
throw new IllegalStateException("Cannot call " +
method.getName() + " as service " + osgiServiceClass + " is not
available");
}
LOG.debug("OsgiServiceProxy: Retrieved service ref for {} ",
this.osgiServiceClass);
Object result = null;
try {
Object service =
componentTypeBundleContext.getService(serviceReference);
result = method.invoke(service, methodArgs);
} finally {
LOG.debug("OsgiServiceProxy: Ungetting service ref for {}",
this.osgiServiceClass);
componentTypeBundleContext.ungetService(serviceReference);
}
return result;
}
}
That's unfortunately correct. What I'm in the process of doing is
changing the OSGiServiceInjector so that if the adaptable is a
request, it uses SlingScriptHelper intead of the bundle context so at
least the references are released if you use a request as the
adaptable. But I don't see a good way to handle this otherwise. Any
ideas?
Regards,
Justin
Cheers,
Konrad
On 24 Dec 2013, at 22:16, Justin Edelson <[email protected]>
wrote:
Thanks everyone for your feedback. I've updated both the wiki and
implementation to include support for:
* declaring an injection as being provided specifically by a
particular injector, using the @Source annotation (as well as
adding
this annotation to @Filter)
* composition support (without new annotations)
* switched package to .annotations from .api
* using BundleTracker rather than BundleListener
* a new injector type for child resources
I also tried to add some reference information to the wiki.
I think this captured all of the feedback received so far. Thanks
again.
Justin
On Sat, Dec 21, 2013 at 1:47 AM, Georg Henzler
<[email protected]> wrote:
Hi all,
first of all I have to say that I'm really happy to see that
effort is being
made to come up with a annotation based model binding mechansim.
We've been
using our own-grown for a while, but a standard is better! :)
I also think it would be useful to inject "sub models". Using only
the
@Inject annotation is ambiguous though, as the class could be
either an OSGi
Service or a sub model. A solution for this could be to use an
annotation
like @SubModel and make OSGi services the default.
@Inject @SubModel
private ImageModel image; // using the field name as context path
for the
sub model as default, in this case ./image
@Inject @SubModel(path="image2") // path explicitly provided, here
./image2
private ImageModel anotherImage;
@Inject // assumed to be an OSGi service for non-primitive types
private SomeOtherClass myService;
-Georg
Am 20.12.2013 15:19, schrieb Justin Edelson:
Hi Konrad,
Thanks for the clarification.
It seems straightforward enough to be able to adapt the injected
value
if it is not assignable to the field's class.
@Inject
private ImageModel image
image would be a Resource object natively which could then be
adapted
to the ImageModel class.
Justin
On Fri, Dec 20, 2013 at 8:08 AM, Konrad Windszus
<[email protected]> wrote:
Hi Justin,
let me give a concrete example where switching resource nodes is
actually
useful: I do have a composition model of two image models (i.e.
the same
class). Obviously they cannot share the same node, as both
models are
referring to the same value names. Therefore an approach similar
to
<sling:include path="..." resourceType=".."> would be very
useful on the
model side. I admit that in the case of models it is a little
bit different,
because we are not doing real request dispatching here. Rather I
want to
have a way to tell the factory (or only the ValueMap injector)
to act on a
certain sub node of the request resource instead of the request
resource
itself. That way we could tell the instance1 of the image model
to act on
subnode 'image1" and the instance2 of that model to act on
subnode "image2".
Regards,
Konrad
Am 20.12.2013 um 13:41 schrieb Justin Edelson
<[email protected]>:
Hi Konrad,
This makes sense, except for the part about "switch the current
resource"? What do you mean by this? It seems we should be
treating
the request resource (which is what I think of as the "current"
resource) as immutable.
Regards,
Justin
On Fri, Dec 20, 2013 at 5:31 AM, Konrad Windszus
<[email protected]>
wrote:
Hi Justin,
another useful feature just came to my mind (in fact we are
using it in
our own annotation framework) which is composition. Would it
be much effort
to allow injecting one model into another?
We do have the following usecase for that (although this is
CQ, I guess
there is a similar usecase in Sling only):
You have a model for an image with title, alternative text.
You have a model for multiline text fields and alignment.
There exist resourceTypes for each of the models as well as a
composite
resourceType multilineImage.
For the composite resourceType I would like to reuse the
existing
models, but I cannot split up the view (i.e. the JSPs and work
with
sling:include), because the html is somehow intertwined.
Therefore I would define another composite model exposing the
models
for both image and multiline and use that composite model in
my JSP.
It would be great if for the injection of other models it
would be
possible to switch the current resource as well (i.e. descent
into subnode
image).
That do you think about that?
Thanks in advance,
Konrad
Am 19.12.2013 um 18:07 schrieb Justin Edelson
<[email protected]>:
Hi,
I've published a page to the wiki about a concept I've been
working on
to consolidate the various appproaches I have seen in the
wild to
model object creation. I'm calling this YAMF for now,
although ideally
we'll just call it Sling Models :)
Without repeating the whole contents of the wiki page, at a
high
level, this is a purely annotation driven approach supporting
both
classes and interfaces. Your model class simply needs to
declare from
which other classes it can be adapted:
@Model(adaptables=Resource.class)
And then annotate the fields (for classes) and methods (for
interfaces) which need injection:
@Inject
private String propertyName;
You can inject properties, OSGi services, request attributes,
and
entries from SlingBindings.
New injector types can be created through an SPI.
Additional annotations are supported for special cases:
@Optional - mark a field/method as optional.
@Filter - provide a filter (i.e. for OSGi services)
@Named - specify a name (other than the default field/method
name) to
use for the inejction lookup.
More detail can be found here:
https://cwiki.apache.org/confluence/display/SLING/YAMF+-+Yet+Another+Model+Factory
The working code is up in my whiteboard:
https://svn.apache.org/repos/asf/sling/whiteboard/justin/yamf/
Look forward to your feedback.
Regards,
Justin