Skip to content

Commit

Permalink
Added a facade source generator
Browse files Browse the repository at this point in the history
---
 Signed-off-by: Peter Kriens <[email protected]>

Signed-off-by: Peter Kriens <[email protected]>
  • Loading branch information
pkriens committed Nov 28, 2023
1 parent 499dd74 commit ba6de7c
Show file tree
Hide file tree
Showing 34 changed files with 852 additions and 959 deletions.
89 changes: 52 additions & 37 deletions biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Binder.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,29 @@

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Supplier;

import org.eclipse.jdt.annotation.Nullable;
import org.osgi.annotation.versioning.ProviderType;

/**
* A Binder establishes a connection between an object used in a non-OSGi
* environment and a corresponding <em>backing service</em>. This connection is
* identified by an opaque <em>id</em>. There should be mostly one unique
* backing service associated with each id. However, multiple binders can
* utilize the same backing service, and each binder receives a distinct object
* representing from the backing service. This is achieved through the use of
* PROTOTYPE services or by explicitly managing the lifecycle with a
* {@link Delegate}.
* A Binder establishes a connection between a <em>facade</em> object used in a
* non-OSGi environment and a delegate implemented as a <em>backing
* service</em>. Any call in the facade is delegated to the delegate service.
* Since the delegate is a service, the service can be absent. The Binder will
* wait a {@link #setTimeout(long)} time before it will throw an exception.
* <p>
* The facade must a {@link FacadeManager#FACADE_ID} during construction. This
* id is assumed to be registered as a service by the delegate backing service.
* The Facade Manager service will track the different parties and inject the
* binder when the service is available.
* <p>
* The primary use case for Binders is in the context of Eclipse Extension
* Points. In this subsystem, objects are created dynamically without contextual
Expand All @@ -28,10 +34,12 @@
* locates the appropriate backing service through the Facade Manager. Given the
* highly dynamic nature of this environment, the Binder handles the absence of
* a backing service by delaying delegation if a backing service is not
* immediately available.
* immediately available. See @see Facade {@link FacadeManager}
* <p>
* The Binder uses statics to be able to interface with the brain dead world out
* there.
*
* @param <D>
* the domain type
* @param <D> the domain type
*/
@ProviderType
public class Binder<D> implements Supplier<D>, Consumer<D>, AutoCloseable {
Expand Down Expand Up @@ -76,20 +84,17 @@ public static void purge() {
* Create a binder and register it locally. If a Facaade Manager is set,
* register there.
*
* @param facade
* the object acting as a facade, only a weak
* reference will be maintained
* @param domainType
* the actual domain type
* @param id
* the service object facade id, see
* {@link FacadeManager#FACADE_ID}
* @param description
* a general description of the facade
* @param facade the object acting as a facade, only a weak reference will
* be maintained
* @param domainType the actual domain type
* @param id the service object facade id, see
* {@link FacadeManager#FACADE_ID}
* @param attributes optional attributes of the facade
*/
public static <T> Binder<T> create(Object facade, Class<?> domainType, String id, String description) {
public static <T> Binder<T> create(Object facade, Class<?> domainType, String id, @Nullable
Map<String, String> attributes) {

Binder<T> binder = new Binder<>(facade, domainType, id, description);
Binder<T> binder = new Binder<>(facade, domainType, id, attributes);

synchronized (binders) {
binders.add(binder);
Expand All @@ -103,21 +108,24 @@ public static <T> Binder<T> create(Object facade, Class<?> domainType, String id
return binder;
}

final String description;
final Map<String, String> attributes;
final WeakReference<?> facade;
final String id;
final Object lock = new Object();
final AtomicReference<AutoCloseable> registration = new AtomicReference<>();
final Class<?> domainType;

long timeout = TimeUnit.SECONDS.toNanos(30);
D current;
Object state;
int cycles;
boolean closed;

Binder(Object facade, Class<?> domainType, String id, String description) {
Binder(Object facade, Class<?> domainType, String id, @Nullable
Map<String, String> attributes) {
this.domainType = domainType;
this.id = id;
this.description = description;
this.attributes = attributes == null ? Collections.emptyMap() : new HashMap<>(attributes);
this.facade = new WeakReference<>(facade);
}

Expand Down Expand Up @@ -168,7 +176,7 @@ public D get() {

synchronized (lock) {
if (closed)
throw new IllegalStateException("the binder with id " + id + " has been closed for " + description);
throw new IllegalStateException("the binder with id " + id + " has been closed for " + attributes);

if (current != null)
return current;
Expand All @@ -178,19 +186,19 @@ public D get() {

while (current == null) {
if ((System.nanoTime() - start) > timeout) {
throw new IllegalStateException(
"no backing service " + id + " can be found for " + description);
throw new IllegalStateException("no backing service " + id + " can be found for " + attributes);
}
lock.wait(1_000);
if (closed)
throw new IllegalStateException(
"the binder with id " + id + " has been closed for " + description);
"the binder with id " + id + " has been closed for " + attributes);
}
return current;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Thread.currentThread()
.interrupt();
throw new IllegalStateException(
"interrupted while waiting for backing service " + id + " for " + description);
"interrupted while waiting for backing service " + id + " for " + attributes);
}
}
}
Expand Down Expand Up @@ -224,17 +232,16 @@ public String getId() {
}

/**
* Get a description of the facade object
* Get the attributes of the facade object
*/
public String getDescription() {
return description;
public Map<String, String> getAttributes() {
return attributes;
}

/**
* Set the timeout to wait for the backing service to be injected.
*
* @param timeout
* timeout in milliseconds
* @param timeout timeout in milliseconds
*/
public void setTimeout(long timeout) {
this.timeout = TimeUnit.MILLISECONDS.toNanos(timeout);
Expand Down Expand Up @@ -275,4 +282,12 @@ public WeakReference<?> getFacade() {
return facade;
}

public long getTimeout() {
return timeout;
}

public Class<?> getDomainType() {
return domainType;
}

}
29 changes: 13 additions & 16 deletions biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Delegate.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
package biz.aQute.bnd.facade.api;

import java.lang.ref.WeakReference;

import org.eclipse.jdt.annotation.Nullable;
import org.osgi.annotation.versioning.ConsumerType;

/**
* Normally it is sufficient to register a PROTOTYPE scoped component to provide
* the backing service for a facade. However, sometimes it is necessary to
* manage the life cycle in more detail.
* This service interface provides managed control of the creation of the
* delegate objects. Normally it is sufficient to register a PROTOTYPE scoped
* component to provide the backing service for a facade. However, sometimes it
* is necessary to manage the life cycle in more detail.
* <p>
* A Delegate is responsible for creating instances of the facade backing
* object. It is associated with a unique ID. There should be only one Delegate
* service with the given id. A Delegate service must be registered with the
* service property {@link FacadeManager#FACADE_ID}
* service property {@link FacadeManager#FACADE_ID} with this id.
*
* @param <D>
* the domain type of the delegate
* @param <D> the domain type of the delegate
*/
@ConsumerType
public interface Delegate<D> {
/**
* The ID of the delegate. This ID must match the ID of the facade.
* The ID of the delegate. This ID must match the ID of the facade. The must
* only be one Delegate or PROTOTYPE scoped service registered with a given
* id.
*/
String getId();

Expand All @@ -31,12 +30,10 @@ public interface Delegate<D> {
* previous instance, maybe from a previous delegate or even Facade Manager,
* will be past to this method.
*
* @param description
* a description about the caller
* @param state
* the current state or null
* @param description a description about the caller
* @param state the current state or null
* @param attributes a set of attributes, use case specific
* @return an instance
*/
Instance<D> create(String description, WeakReference<?> facade, @Nullable
Object state);
Instance<D> create(Binder<D> binder);
}
25 changes: 15 additions & 10 deletions biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/FacadeManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,35 @@
import org.osgi.annotation.versioning.ProviderType;

/**
* The Facade Manager manages facades that delegate to a backing service. The
* facades are constant objects that are maintained by ignorant code that has no
* clue about dynamics. In OSGi, however, the backing service can come and go,
* and event the Facade Manger is not required to be constant.
* The Facade Manager manages Binder objects that connect a facade (a normal
* object) to a delegate, dynamic OSGi service. The Binder mediates between a
* facade that delegates all its methods to a delegate. Since the delegate is
* dynamic, this Facade Manager will inform the Binder of the presence of the
* service so delegation can be postponed until the service is available.
* <p>
* A Binder is created by the facade object. This binder, tracks the Facade
* Manager and registers when found.
* Since the facade is a normal Java object, there is no explicit life cycle.
* This Facade Manager will periodically poll the Binder objects to detect when
* the facade object is garbage collected.
* <p>
* This is a singleton service, not other instance is allowed.
*/
@ProviderType
public interface FacadeManager {

/**
* The key for a service property. This is the unique id of a backing
* service. Only one service should be registered with this id at any moment
* in time. The value must be a String.
* service. At most one service should be registered with this id at any
* moment in time. The value must be a String.
*/
String FACADE_ID = "facade.id";

/**
* Register a binder. For a given binder, this should happen only once per
* Register a binder. For a given Binder, this must happen only once per
* registered Facade Manager.
*
* @param binder the binder to register
* @return a closeable that will unregister this binder when closed.
* @return a closeable that will unregister this binder is closed from the
* facade.
*/
AutoCloseable register(Binder<?> binder);
}
12 changes: 5 additions & 7 deletions biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Instance.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,28 @@

/**
* Represents an instance created by a Delegate. The instance must automatically
* be closed when the Delegate is unregistered.
* be closed when the Delegate is unregistered. It can also be closed manually.
*
* @param <D>
* the type of the domain
* @param <D> the type of the domain
*/
@ConsumerType
public interface Instance<D> extends AutoCloseable {

/**
* Get the backing object. This is never null.
* Get the delegate object. This is never null.
*/
D get();

/**
* Get the current state of the backing object. This state is passed to the
* {@link Delegate#create(String, java.lang.ref.WeakReference, Object)} when
* it needs to be recreated.
* {@link Delegate#create(Binder)} when it needs to be recreated.
*/
@Nullable
Object getState();

/**
* Close the instance. This will free the backing object. This method can be
* called multiple times but will only work the first time.
* called multiple times but will only do work the first time.
*/
@Override
void close();
Expand Down
3 changes: 0 additions & 3 deletions biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Memento.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
import org.osgi.annotation.versioning.ConsumerType;

/**
* An interface that can optionally be implemented by the backing service. The
* Delegate can use it to get the state when the backing service goes away and
* restore the state when it returns.
* <p>
* The return object must take care not to use domain classes. It should be
* possible to recycle the bundle of the implementation between
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package biz.aQute.bnd.facade.provider;

import java.lang.ref.WeakReference;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Map.Entry;
Expand Down Expand Up @@ -125,9 +124,7 @@ private Instance bind(Delegate delegate, Binder binder) {

assert binder.getId().equals(delegate.getId());

Object state = binder.getState();
WeakReference ref = binder.getFacade();
Instance instance = delegate.create(binder.getDescription(), binder.getFacade(), state);
Instance instance = delegate.create(binder);
binder.setState(null);
binder.accept(instance.get());
return instance;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package biz.aQute.bnd.facade.provider;

import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicBoolean;

import org.osgi.framework.ServiceObjects;

import biz.aQute.bnd.facade.api.Binder;
import biz.aQute.bnd.facade.api.Delegate;
import biz.aQute.bnd.facade.api.Instance;
import biz.aQute.bnd.facade.api.Memento;
Expand All @@ -18,10 +18,10 @@ class InstanceImpl implements Instance<D> {
final D service = factory.getService();
final AtomicBoolean closed = new AtomicBoolean(false);

InstanceImpl(String description, WeakReference<?> facade, Object state) {
InstanceImpl(Binder<?> binder) {
assert service != null;
if (service instanceof Memento m) {
m.setState(state, facade);
m.setState(binder.getState(), binder.getFacade());
}
}

Expand Down Expand Up @@ -64,7 +64,7 @@ public String getId() {
}

@Override
public Instance<D> create(String description, WeakReference<?> facade, Object state) {
return new InstanceImpl(description, facade, state);
public Instance<D> create(Binder<D> binder) {
return new InstanceImpl(binder);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ void deactivate() {
}

public static class DomainFacade implements Domain {
final Binder<Domain> binder = Binder.create(this, Domain.class, ID, "description");
final Binder<Domain> binder = Binder.create(this, Domain.class, ID, null);

@Override
public void set(String s) {
Expand Down
Loading

0 comments on commit ba6de7c

Please sign in to comment.