Configuration with custom properties
PropertiesModule
can extend any entity with a set of key/value properties which are linked to the entity.
This is useful for cases where you are writing your own module and wish to allow consumers of your module to registry their own custom properties, linked to an entity inside your module.
To achieve this, you will have to implement and provide certain classes.
The following examples extend a Client
entity with custom properties.
Custom properties class
If you want to extend existing entities with properties, you will have to create a holder class for these properties. A simple class can look like this:
public class ClientProperties extends EntityProperties<Long>
{
private final Long clientId;
public ClientProperties( long clientId,
PropertyTypeRegistry<String> propertyTypeRegistry,
PropertiesSource source ) {
super( propertyTypeRegistry, source );
this.clientId = clientId;
}
@Override
public Long getId() {
return clientId;
}
}
Custom properties installer
In order to extend existing entities with properties, the tables for these properties have to be installed. The client application can do this by registering installers that extend from EntityPropertiesInstaller or RevisionBasedEntityPropertiesInstaller.
@Installer(description = "Installs the client properties table", version = 1)
public class ClientPropertiesSchemaInstaller extends EntityPropertiesInstaller
{
private final SchemaConfiguration schemaConfiguration;
public ClientPropertiesSchemaInstaller( SchemaConfiguration schemaConfiguration ) {
this.schemaConfiguration = schemaConfiguration;
}
@Override
protected String getTableName() {
return schemaConfiguration.getCurrentTableName( "table_client" ); (1)
}
@Override
protected String getKeyColumnName() {
return "client_id"; (2)
}
}
1 | Refers to your table name of your entity |
2 | Refers to the primary key of your entity |
Custom properties repository
The Properties module offers a base implementation of EntityPropertiesRepository that can be extended with a typed implementation. It might be useful to add caching to this layer.
@Repository
public class ClientPropertiesRepository extends EntityPropertiesRepository<Long>
{
public ClientPropertiesRepository( EntityPropertiesDescriptor configuration ) {
super( configuration );
}
@Cacheable("clientPropertiesCache")
@Transactional(readOnly = true)
@Override
public StringPropertiesSource loadProperties( Long entityId ) {
return super.loadProperties( entityId );
}
@CacheEvict(value = "clientPropertiesCache", key = "#entityId")
@Transactional
@Override
public void saveProperties( Long entityId, StringPropertiesSource properties ) {
super.saveProperties( entityId, properties );
}
@CacheEvict(value = "clientPropertiesCache", key = "#entityId")
@Transactional
@Override
public void deleteProperties( Long entityId ) {
super.deleteProperties( entityId );
}
}
Custom properties service
The client application should then extend either AbstractEntityPropertiesService or AbstractRevisionBasedEntityPropertiesService. These services can then be wired into the entity services and used as described below.
ClientPropertiesService
public interface ClientPropertiesService extends EntityPropertiesService<ClientProperties, Long>
{
}
ClientPropertiesService
@Service
public class ClientPropertiesServiceImpl extends AbstractEntityPropertiesService<ClientProperties, Long> implements ClientPropertiesService
{
public ClientPropertiesServiceImpl( EntityPropertiesRegistry entityPropertiesRegistry,
EntityPropertiesRepository<Long> entityPropertiesRepository ) {
super( entityPropertiesRegistry, entityPropertiesRepository );
}
@Override
protected ClientProperties createEntityProperties( Long entityId,
PropertyTypeRegistry<String> propertyTypeRegistry,
StringPropertiesSource source ) {
return new ClientProperties( entityId, propertyTypeRegistry, source );
}
}
Custom properties registry
The entities with properties should have a registry class extending from EntityPropertiesRegistry. Registries are classes that contain a map for the properties added to a specific entity. For example, a Client entity class can have a corresponding ClientPropertiesRegistry class.
ClientPropertiesRegistry
@Service
public class ClientPropertiesRegistry extends EntityPropertiesRegistry
{
public ClientPropertiesRegistry( EntityPropertiesDescriptor descriptor ) {
super( descriptor );
}
}
Custom properties configuration
The properties for a specific entity are configured in a configuration class extending from AbstractEntityPropertiesConfiguration. This class will specify the entity class, table name, the identifying column name and wire all classes extended as described above.
ClientPropertiesConfiguration
@Configuration
public class ClientPropertiesConfiguration extends AbstractEntityPropertiesConfiguration
{
public static final String ID = ClientModule.NAME + ".ClientProperties";
@Override
public Class<?> entityClass() {
return Client.class;
}
@Override
public String propertiesId() {
return ID;
}
@Override
protected String originalTableName() {
return "table_client_properties";
}
@Override
public String keyColumnName() {
return "client_id";
}
@Bean(name = "clientPropertiesService")
@Override
public ClientPropertiesService service() {
return new ClientPropertiesServiceImpl( registry(), clientPropertiesRepository() );
}
@Bean
public ClientPropertiesRepository clientPropertiesRepository() {
return new ClientPropertiesRepository( this );
}
@Bean(name = "clientPropertiesRegistry")
@Override
public ClientPropertiesRegistry registry() {
return new ClientPropertiesRegistry( this );
}
}
Custom property registration
The comsuming application should register the properties during application startup as follows:
@Autowired
public void registerProperties( ClientPropertiesRegistry clientPropertiesRegistry,
AcrossModule currentModule ) {
clientPropertiesRegistry.register( currentModule, "client_code",
TypeDescriptor.valueOf( String.class ) );
}
This snippet should be contained in a configuration class.
This registry contains the definition of the property key to its implementation class and optionally a default value. See EntityPropertiesRegistry for all available options.
Providing a default value for a custom property
The Properties module supports simple types and parametrized types for registries. When registering a mapping, the client application can supply a default value. These defaults should not be changed by client code. In order to enforce this, the Properties module uses PropertyFactory from the Foreach common libraries as a way to construct these defaults. TypeDescriptors (from Spring) are used to describe the parameter types.
// A SingletonPropertyFactory used for an enum:
clientPropertiesRegistry.register( currentModule, "enum_property", AnEnum.class,
SingletonPropertyFactory.<String, AnEnum>forValue( AnEnum.SOME_VALUE ) );
// An anonymous implementation of PropertyFactory for a Set of Foo (which is an entity):
clientPropertiesRegistry.register( currentModule,
"foo_property",
TypeDescriptor.collection( Set.class, TypeDescriptor.valueOf( Foo.class ) ),
new PropertyFactory<String, Object>()
{
@Override
public Object create( PropertyTypeRegistry registry, String propertyKey ) {
return new HashSet<Foo>();
}
}
);