Goal

In this tutorial, we add support for a custom ISO 19115 metadata format.

By default, LuciadLightspeed offers a decoder for the ISO 19139 format, which defines how to store ISO 19115 metadata in the XML format. You may have metadata defined in a custom format, though. In that case, you must create a decoder yourself.

In this tutorial, we create a custom decoder that decodes an imaginary metadata format into ISO 19115 metadata objects.

The custom metadata format

The metadata format we’re going to support is a fictitious metadata format based on the standard Java properties file format.

For this tutorial, we implement 3 properties: title, abstract, and keywords.

For example:

# Custom metadata file for States.shp
title = US States
abstract = States of the United States.
keywords = state,federation

Creating the metadata decoder

Creating a metadata decoder comes down to implementing the ILcdMetadataDecoder interface.

We start by implementing the canDecodeMetadata method. This method checks if the metadata from a given source name can be decoded. For performance reasons, we want to make this check fast, and allow it to return false positives. That’s why we inspect only the file extension, not the content itself.

@Override
public boolean canDecodeMetadata(String aSourceName) {
  return TLcdStringUtil.endsWithIgnoreCase(aSourceName, ".properties");
}

Next, we implement the decodeMetadata method. This method tries to decode the metadata from a given source name. For our custom decoder, the source name will point to a Java Property file. We read the title, abstract and keywords properties from it and put them in the resulting TLcdISO19115Metadata object.

@Override
public TLcdISO19115Metadata decodeMetadata(String aSourceName) throws IOException {
  if (!canDecodeMetadata(aSourceName)) {
    throw new IOException("Cannot decode " + aSourceName);
  }

  Properties properties = new Properties();
  try (InputStream inputStream = fInputStreamFactory.createInputStream(aSourceName)) {
    properties.load(inputStream);
  }

  TLcdISO19115Metadata metadata = new TLcdISO19115Metadata();
  TLcdISO19115Identification identification = new TLcdISO19115DataIdentification();
  metadata.getIdentificationInfo().add(identification);

  // the title
  String titleValue = properties.getProperty("title");
  if (titleValue != null) {
    TLcdISO19115Citation citation = new TLcdISO19115Citation();
    identification.setCitation(citation);
    citation.setTitle(titleValue);
  }

  String abstractValue = properties.getProperty("abstract");
  if (abstractValue != null) {
    identification.setAbstract(abstractValue);
  }

  String keywordsValue = properties.getProperty("keywords");
  if (keywordsValue != null) {
    TLcdISO19115Keywords keywords = new TLcdISO19115Keywords();
    keywords.getKeyword().addAll(Arrays.asList(keywordsValue.split(",")));
    identification.getDescriptiveKeywords().add(keywords);
  }

  return metadata;
}

Finally, we implement the findAndDecodeMetadata method. This method accepts the source name of a data file. Using that source name, it searches for additional metadata files and tries to decode them. In our implementation, we look for files having the same name as the data file, but with a .properties extension.

@Override
public MetadataWithSource findAndDecodeMetadata(String aSourceName) throws IOException {
  if (TLcdStringUtil.endsWithIgnoreCase(aSourceName, ".properties")) {
    // The data file itself is a .properties file. There cannot exist another dedicated .properties
    // metadata file with the same name!
    return null;
  }

  String propertyFileName = toPropertyFileName(aSourceName);
  if (propertyFileName == null) {
    return null;
  }

  return new MetadataWithSource(decodeMetadata(propertyFileName),
                                new TLcdModelMetadata.Source(propertyFileName, "text/plain"));
}

/**
 * Translates the given source name of the data file into the name of the properties file.
 */
private String toPropertyFileName(String aSourceName) {
  String baseName = aSourceName.substring(0, aSourceName.lastIndexOf('.'));
  String propertyFileName = baseName + ".properties";

  // check if the property file exists
  if (Files.exists(Paths.get(propertyFileName))) {
    return propertyFileName;
  }

  return null;
}

Optional: registering the metadata decoder as a service

You typically want your application to discover and use custom decoders automatically. For this purpose, we recommend that you register your metadata decoder as a service. You can do so by adding an annotation to the class:

@LcdService(service = ILcdMetadataDecoder.class)
public class PropertiesMetadataDecoder implements ILcdMetadataDecoder {

Now, when you need to decode metadata, you can use a composite metadata decoder instance that collects all registered metadata decoders, including your custom metadata decoder.

ILcdMetadataDecoder metadataDecoder =
    new TLcdCompositeMetadataDecoder(TLcdServiceLoader.getInstance(ILcdMetadataDecoder.class));

For more information about how the services mechanism works, see here.

Full code

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Properties;

import com.luciad.format.metadata.model.citation.TLcdISO19115Citation;
import com.luciad.format.metadata.model.identification.TLcdISO19115DataIdentification;
import com.luciad.format.metadata.model.identification.TLcdISO19115Identification;
import com.luciad.format.metadata.model.identification.TLcdISO19115Keywords;
import com.luciad.format.metadata.model.metadataentityset.TLcdISO19115Metadata;
import com.luciad.io.ILcdInputStreamFactory;
import com.luciad.io.TLcdCompositeInputStreamFactory;
import com.luciad.model.TLcdModelMetadata;
import com.luciad.util.TLcdStringUtil;
import com.luciad.util.service.LcdService;
import com.luciad.util.service.TLcdServiceLoader;

/**
 * This class demonstrates how to implement a custom metadata decoder.
 */
@LcdService(service = ILcdMetadataDecoder.class)
public class PropertiesMetadataDecoder implements ILcdMetadataDecoder {

  private final ILcdInputStreamFactory fInputStreamFactory =
      new TLcdCompositeInputStreamFactory(TLcdServiceLoader.getInstance(ILcdInputStreamFactory.class));

  @Override
  public boolean canDecodeMetadata(String aSourceName) {
    return TLcdStringUtil.endsWithIgnoreCase(aSourceName, ".properties");
  }

  @Override
  public TLcdISO19115Metadata decodeMetadata(String aSourceName) throws IOException {
    if (!canDecodeMetadata(aSourceName)) {
      throw new IOException("Cannot decode " + aSourceName);
    }

    Properties properties = new Properties();
    try (InputStream inputStream = fInputStreamFactory.createInputStream(aSourceName)) {
      properties.load(inputStream);
    }

    TLcdISO19115Metadata metadata = new TLcdISO19115Metadata();
    TLcdISO19115Identification identification = new TLcdISO19115DataIdentification();
    metadata.getIdentificationInfo().add(identification);

    // the title
    String titleValue = properties.getProperty("title");
    if (titleValue != null) {
      TLcdISO19115Citation citation = new TLcdISO19115Citation();
      identification.setCitation(citation);
      citation.setTitle(titleValue);
    }

    String abstractValue = properties.getProperty("abstract");
    if (abstractValue != null) {
      identification.setAbstract(abstractValue);
    }

    String keywordsValue = properties.getProperty("keywords");
    if (keywordsValue != null) {
      TLcdISO19115Keywords keywords = new TLcdISO19115Keywords();
      keywords.getKeyword().addAll(Arrays.asList(keywordsValue.split(",")));
      identification.getDescriptiveKeywords().add(keywords);
    }

    return metadata;
  }

  @Override
  public MetadataWithSource findAndDecodeMetadata(String aSourceName) throws IOException {
    if (TLcdStringUtil.endsWithIgnoreCase(aSourceName, ".properties")) {
      // The data file itself is a .properties file. There cannot exist another dedicated .properties
      // metadata file with the same name!
      return null;
    }

    String propertyFileName = toPropertyFileName(aSourceName);
    if (propertyFileName == null) {
      return null;
    }

    return new MetadataWithSource(decodeMetadata(propertyFileName),
                                  new TLcdModelMetadata.Source(propertyFileName, "text/plain"));
  }

  /**
   * Translates the given source name of the data file into the name of the properties file.
   */
  private String toPropertyFileName(String aSourceName) {
    String baseName = aSourceName.substring(0, aSourceName.lastIndexOf('.'));
    String propertyFileName = baseName + ".properties";

    // check if the property file exists
    if (Files.exists(Paths.get(propertyFileName))) {
      return propertyFileName;
    }

    return null;
  }
}