Goal
In this tutorial, we add support for custom properties using the LuciadFusion API.
As discussed in the Using custom properties with Data article, you add support for custom properties to LuciadFusion by plugging in a custom property decoder. In this tutorial, we create such a custom property decoder and make it available to LuciadFusion.
The example
In this tutorial we define two custom properties:
-
status : the status of the data.
-
license : the license applied to the data.
We assume that the license of a data resource is stored in a file that has the same name as the data source, but has a .license
extension.
For instance, if we have a data file /data/world.shp
, the file holding the license is /data/world.license
.
We don’t define a custom metadata decoder, which means that our custom property decoder has to search and read that file by
itself.
The status property comes from the ISO metadata generated by LuciadFusion.
Create a custom property decoder
Creating a custom property decoder comes down to implementing the ILfnCustomPropertyDecoder
interface.
public class CustomPropertyDecoderImpl implements ILfnCustomPropertyDecoder {
}
The ILfnCustomPropertyDecoder
interface has two methods that we need to implement:
-
getPropertyDefinitions
: here we define the possible set of custom properties. -
decode
: here we decode the custom property values
Define the custom properties
We start by defining our custom properties. To do this, we implement the getPropertyDefinitions
method.
There we define a name and type for every possible property.
Optionally we can define a display name and mark the property as queryable, meaning that we can use it in expressions when
querying for data.
While it’s not required to define all custom properties that the decoder can return, it’s highly recommended to do so. Undefined properties don’t have a display name and aren’t queryable. In addition, LuciadFusion won’t be able to verify that the type of decoded custom properties matches the type of its definition. |
In our example, we define two properties.
The 'status' property, which is defined by ISO 19115, has a fixed set of possible values of type String
.
We want to be able to search for data with a specific status, so we mark that property as queryable.
The 'license' property is a regular string property containing the license applied to the data.
This is typically a lot of text which we won’t use in query expressions, so we mark that property as non-queryable.
This method returns a TLfnCustomPropertyDefinitions
object, which we can create using a TLfnCustomPropertyDefinitions.Builder
.
We add the custom properties to this builder one by one. For every property, we must specify at least its name and type. In
addition, we can give it a display name, and we can also mark this property as queryable.
Calling the build()
method at the end creates the TLfnCustomPropertyDefinitions
object with all defined custom properties.
private static final String STATUS_PROPERTY = "status";
private static final String LICENSE_PROPERTY = "license";
private static final Set<String> STATUS_VALUES = new HashSet<>(Arrays.asList(
"completed", "obsolete", "planned", "required", "historicalArchive", "onGoing", "underDevelopment"
));
@Override
public TLfnCustomPropertyDefinitions getPropertyDefinitions() {
return TLfnCustomPropertyDefinitions.newBuilder()
.addStringProperty(STATUS_PROPERTY, "Status", STATUS_VALUES, true)
.addStringProperty(LICENSE_PROPERTY, "License", false)
.build();
}
Decode the custom properties
Next, we implement the decode
method. To make the implementation easier to read, we create two helper methods first: one for
decoding the status property, and another one for decoding the license property.
We start by decoding the 'status' property from a TLcdISO19115Metadata
object.
/**
* Decodes the 'status' property from the ISO metadata.
*/
private TLfnCustomProperty decodeStatus(TLcdISO19115Metadata aMetadata) {
List<TLcdISO19115Identification> identificationInfo = aMetadata.getIdentificationInfo();
if (identificationInfo != null && !identificationInfo.isEmpty()) {
List<TLcdISO19115ProgressCode> statusCodes = identificationInfo.get(0).getStatus();
if (statusCodes != null && !statusCodes.isEmpty()) {
String status = statusCodes.get(0).getValueObject();
return TLfnCustomProperty.newBuilder().name(STATUS_PROPERTY).stringValue(status).build();
}
}
return null;
}
The 'license' property isn’t in a TLcdISO19115Metadata
object, but in a file next to the data file.
The name of the file holding the license is the same as the name of the data file, but with a .license
extension.
So, to decode this 'license' property, we first search for this file. If it exists, we read it and return the content as the
'license' property.
This method returns the license property itself, and the location of the license file too.
/**
* Decodes the 'license' property from the license file located next to the data file itself.
*/
private TLcdPair<TLfnCustomProperty, TLcdModelMetadata.Source> decodeLicense(String aSourceName) throws IOException {
String licenseFileName = toLicenseFileName(aSourceName);
if (licenseFileName == null) {
return null;
}
String license;
try (InputStream inputStream = Files.newInputStream(Paths.get(licenseFileName))) {
license = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))
.lines()
.collect(Collectors.joining("\n"));
}
return new TLcdPair<>(
TLfnCustomProperty.newBuilder().name(LICENSE_PROPERTY).stringValue(license).build(),
new TLcdModelMetadata.Source(licenseFileName, "text/plain")
);
}
/**
* Translates the given source name of the data file into the name of the '.license' file.
*/
private String toLicenseFileName(String aSourceName) {
String baseName = aSourceName.substring(0, aSourceName.lastIndexOf('.'));
String licenseFileName = baseName + ".license";
// check if the property file exists
if (Files.exists(Paths.get(licenseFileName))) {
return licenseFileName;
}
return null;
}
With these helper methods, we can now implement the decode
method.
@Override
public CustomPropertiesWithSource decode(String aSourceName, TLcdISO19115Metadata aMetadata) throws IOException {
List<TLfnCustomProperty> properties = new ArrayList<>();
List<TLcdModelMetadata.Source> sources = new ArrayList<>();
// decode the status property
TLfnCustomProperty status = decodeStatus(aMetadata);
if (status != null) {
properties.add(status);
}
// decode the license property
TLcdPair<TLfnCustomProperty, TLcdModelMetadata.Source> license = decodeLicense(aSourceName);
if (license != null) {
properties.add(license.getKey());
sources.add(license.getValue());
}
return new CustomPropertiesWithSource(properties, sources);
}
Spring integration
Now we’ve created the class that defines and decodes the custom properties, we need to make sure that LuciadFusion can pick it up.
To do so, we must configure a Spring bean for this class so that LuciadFusion can pick it up from the Spring application context.
To configure the Spring bean, we create a class and mark it with the @Configuration
annotation.
In this class, we define a Spring bean for our implementation of ILfnCustomPropertyDecoder
.
Make sure to add the package of this class to the Spring application context using the additionalScanPackages
property.
Set this property in the config/fusion.common.yml
file, or create a new Spring profile.
@Configuration
public class CustomPropertiesConfiguration {
@Bean
public ILfnCustomPropertyDecoder customPropertyDecoder() {
return new CustomPropertyDecoderImpl();
}
}
Conclusion
In this tutorial, we illustrated how to define and decode custom properties. For more information about accessing custom properties and searching for data items based on custom property values, see the Using custom properties with Data article.