Reflection-based proxy engine to encapsulate sensitive objects in beans. The core idea is that given some object that contains information relevant in i.e. a script engine but contains sensitive information, you can easily encapsulate it.
For this example, we will only encapsulate one class, RealPerson
.
The sensitive class contains some information we want to expose to a script engine, such as the name and address, but for the sake of this example not the age.
public class RealPerson {
public Integer getAge() {
return 19;
}
public String getName() {
return "Name";
}
public RealAddress getAddress() {
return new RealAddress();
}
}
We can now create an interface to act as the object that will be initialized by the engine and can be exposed to the script engine.
public interface Person extends Wrappable {
@Wrap("getName")
String whatTheyCallMe();
}
Two things are important here:
- The interface must extend Wrappable, this is not a technical limitation but rather a forced convention.
- The annotation on each method denotes which source method of the object we are encapsulating to invoke.
For example, we are invoking
RealPerson#getName
and binding it toPerson#whatTheyCallMe
.
Next we need to initialize the engine.
GuardianContext context = new GuardianContext();
context.associate(RealPerson.class, Person.class);
Finally, we can encapsulate the object:
Person person = context.wrap(new RealPerson());
The underlying concepts work recursively. On top of RealPerson
, also consider RealAddress
.
On top of the already provided above code, let us define the address as:
public class RealAddress {
public Integer getHouse() {
return 123;
}
public String getStreet() {
return "Street";
}
public String getCity() {
return "City";
}
public RealCountry getCountry() {
return new RealCountry();
}
}
Which shall be encapsulated by:
public interface Address extends Wrappable {
@Wrap("getStreet")
String getStreet();
@Wrap("getCity")
String getCity();
@Wrap("getCountry")
Country getCountry();
}
After extending the context as shown below, the behaviour will work as expected:
GuardianContext context = new GuardianContext();
context.associate(RealPerson.class, Person.class);
context.associate(RealAddress.class, Address.class);
In order to work as expected, these are the specifications.
- Every encapsulating object must be an interface extending
Wrappable
. - Every method must be annotated by
@Wrap
and the referencing method name must be provided, non-null and non-empty. - Every referenced method must meet the following criteria:
- The method must be
public
or otherwise accessible without the use ofMethod#setAccessible
. - The method must return a non-primitive object, be of return type
void
or returnnull
. - The method must not return an array of any dimension.
- The method must not have any parameters. By default, the method will be looked up using zero parameters. As such, any method with parameters will simply not be found.
- The method must be
- If the return value of a represented object returns a type that mismatches the return type of the encapsulating object's method, the following will happen:
- If the return value is a registered association in the context, the return value itself will also be transformed recursively.
- If not, an error will be thrown.
Coming soon.
The following features are currently planned, and specified in no order:
- Support for primitive data types as well as arrays.
- Methods that return
Collection
orMap
having their elements (values forMap
) scanned and if applicable mapped to other encapsulated objects. - Ability to choose how the reflection is performed and adding ReflectASM as an option.
- Adding support for setters. The details of this aren't clear yet.