All Downloads are FREE. Search and download functionalities are using the official Maven repository.

thredds.client.catalog.builder.CatalogBuilder Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package thredds.client.catalog.builder;

import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.jdom2.input.SAXBuilder;
import thredds.client.catalog.*;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants.DataFormatType;
import ucar.nc2.constants.FeatureType;
import ucar.nc2.time.Calendar;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarDateFormatter;
import ucar.nc2.units.DateRange;
import ucar.nc2.units.DateType;
import ucar.nc2.units.TimeDuration;
import ucar.nc2.util.URLnaming;
import ucar.unidata.util.StringUtil2;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;

/**
 * Builds client Catalogs using JDOM2
 * Non validating.
 *
 * @author caron
 * @since 1/8/2015
 */
public class CatalogBuilder {
  private static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(CatalogBuilder.class);

  //////////////////////////////////////////////////////////////////////////////////
  // used when reading from XML
  private Map serviceMap = new HashMap<>(); // LOOK why not instead of services ?
  protected Formatter errlog = new Formatter();
  protected boolean fatalError;

  public Catalog buildFromLocation(String location, URI baseURI) {
    location = StringUtil2.replace(location, "\\", "/");

    if (baseURI == null) {
      try {
        baseURI = new URI(location);
      } catch (URISyntaxException e) {
        errlog.format("Bad location = '%s' err='%s'%n", location, e.getMessage());
        logger.error("Bad location = '{}' err='{}}'", location, e.getMessage());
        fatalError = true;
        return null;
      }
    }

    this.baseURI = baseURI;
    try {
      validatePort(baseURI);
    } catch (IllegalArgumentException e) {
      errlog.format("Invalid port number = '%s' err='%s'%n ", baseURI.toASCIIString(), e.getMessage());
      logger.error("Invalid port number = '{}' err='{}}'", baseURI.toASCIIString(), e.getMessage());
      fatalError = true;
      return null;
    }
    readXML(location);
    return fatalError ? null : makeCatalog();
  }

  public Catalog buildFromURI(URI uri) {
    this.baseURI = uri;
    try {
      validatePort(baseURI);
    } catch (IllegalArgumentException e) {
      errlog.format("Invalid port number = '%s' err='%s'%n ", baseURI.toASCIIString(), e.getMessage());
      logger.error("Invalid port number = '{}' err='{}}'", baseURI.toASCIIString(), e.getMessage());
      fatalError = true;
      return null;
    }
    readXML(uri);
    return fatalError ? null : makeCatalog();
  }

  public Catalog buildFromCatref(CatalogRef catref) {
    URI catrefURI = catref.getURI();
    if (catrefURI == null) {
      errlog.format("Catref doesnt have valid UrlPath=%s%n", catref.getUrlPath());
      logger.error("Catref doesnt have valid UrlPath={}", catref.getUrlPath());
      fatalError = true;
      return null;
    }
    this.baseURI = catrefURI;
    try {
      validatePort(baseURI);
    } catch (IllegalArgumentException e) {
      errlog.format("Invalid port number = '%s' err='%s'%n ", baseURI.toASCIIString(), e.getMessage());
      logger.error("Invalid port number = '{}' err='{}}'", baseURI.toASCIIString(), e.getMessage());
      fatalError = true;
      return null;
    }
    Catalog result = buildFromURI(catrefURI);
    catref.setRead(!fatalError);
    return fatalError ? null : result;
  }

  public Catalog buildFromString(String catalogAsString, URI docBaseUri) {
    this.baseURI = docBaseUri;
    try {
      validatePort(baseURI);
    } catch (IllegalArgumentException e) {
      errlog.format("Invalid port number = '%s' err='%s'%n ", baseURI.toASCIIString(), e.getMessage());
      logger.error("Invalid port number = '{}' err='{}}'", baseURI.toASCIIString(), e.getMessage());
      fatalError = true;
      return null;
    }
    readXMLfromString(catalogAsString);
    return fatalError ? null : makeCatalog();
  }

  public Catalog buildFromStream(InputStream stream, URI docBaseUri) {
    this.baseURI = docBaseUri;
    try {
      validatePort(baseURI);
    } catch (IllegalArgumentException e) {
      errlog.format("Invalid port number = '%s' err='%s'%n ", baseURI.toASCIIString(), e.getMessage());
      logger.error("Invalid port number = '{}' err='{}}'", baseURI.toASCIIString(), e.getMessage());
      fatalError = true;
      return null;
    }
    readXML(stream);
    return fatalError ? null : makeCatalog();
  }

  public Catalog buildFromJdom(Element root, URI docBaseUri) {
    this.baseURI = docBaseUri;
    try {
      validatePort(baseURI);
    } catch (IllegalArgumentException e) {
      errlog.format("Invalid port number = '%s' err='%s'%n ", baseURI.toASCIIString(), e.getMessage());
      logger.error("Invalid port number = '{}' err='{}}'", baseURI.toASCIIString(), e.getMessage());
      fatalError = true;
      return null;
    }
    readCatalog(root);
    return fatalError ? null : makeCatalog();
  }

  /*
   * Validates port number of given URI.
   * URI already meets RFC 2396 to pass creation.
   * We are checking to make sure port # isn't something nefarious.
   *
   * @param baseURI java.net.URI to validate
   * 
   * @throws IllegalArgumentException if URI port number if
   * 
   * @see 
   */
  private void validatePort(URI baseURI) throws IllegalArgumentException {
    int port = baseURI.getPort();
    // -1 means port undefined
    if (port != -1) {
      // TCP/IP port numbers below 1024 are only for root user.
      if (port < 1024) {
        if (port != 80 && port != 443) {
          throw new IllegalArgumentException(
              "User requesting access to catalog on non-valid root-privileged port: " + Integer.toString(port));
        }
      }
    }
  }

  public String getErrorMessage() {
    return errlog.toString();
  }

  public String getValidationMessage() {
    return errlog.toString();
  }

  public boolean hasFatalError() {
    return fatalError;
  }

  ////////////////////////////////////////////////////
  protected String name, version;
  protected CalendarDate expires;
  protected URI baseURI;
  protected List properties;
  protected List services;
  protected List datasetBuilders;

  public CatalogBuilder() {}

  public CatalogBuilder(Catalog from) {
    this.name = from.getName();
    this.expires = from.getExpires();
    this.baseURI = from.getBaseURI();
    this.properties = from.getProperties();
    this.services = from.getServices(); // ??
  }

  public void setName(String name) {
    this.name = name;
  }

  public void setBaseURI(URI baseURI) {
    this.baseURI = baseURI;
  }

  public void setExpires(CalendarDate expires) {
    this.expires = expires;
  }

  public void setVersion(String version) {
    this.version = version;
  }

  public void addProperty(Property p) {
    if (p == null) {
      return;
    }
    if (properties == null) {
      properties = new ArrayList<>();
    }
    if (!properties.contains(p)) {
      // O(n^2) but only done once
      properties.add(p);
    }
  }

  public void addService(Service s) {
    if (s == null) {
      return;
    }
    if (services == null) {
      services = new ArrayList<>();
    }
    if (!services.contains(s) && !containsService(s.getName())) {
      services.add(s);
    }
  }

  private boolean containsService(String name) {
    if (name == null) {
      return false;
    }
    if (services == null) {
      return false;
    }
    for (Service s : services) {
      if (name.equals(s.getName())) {
        return true;
      }
    }
    return false;
  }

  public void removeAnyService() {
    services = null;
  }

  public void addDataset(DatasetBuilder d) {
    if (d == null) {
      return;
    }
    if (datasetBuilders == null) {
      datasetBuilders = new ArrayList<>();
    }
    datasetBuilders.add(d);
  }

  ////////////////////////////////////////////////////////////////

  public Catalog makeCatalog() {
    setServices(getDatasets());
    Map flds = setFields();
    return new Catalog(baseURI, name, flds, datasetBuilders);
  }

  // pull services out of the datasets and into the catalog
  private void setServices(Iterable dsIter) {
    for (DatasetBuilder dsb : dsIter) {
      for (Service s : dsb.getServices()) {
        addService(s);
      }
      setServices(dsb.getDatasets()); // recurse
    }
  }

  protected Map setFields() {
    Map flds = new HashMap<>(10);

    if (expires != null) {
      flds.put(Catalog.Expires, expires);
    }
    if (version != null) {
      flds.put(Catalog.Version, version);
    }
    if (services != null) {
      flds.put(Catalog.Services, services);
    }
    if (properties != null) {
      flds.put(Dataset.Properties, properties);
    }

    return flds;
  }

  ///////////////////////////////////////////////////////////
  // get

  @Nullable
  public DatasetBuilder getTopDataset() {
    if (datasetBuilders == null) {
      return null;
    }
    return datasetBuilders.get(0);
  }

  public Iterable getDatasets() {
    if (datasetBuilders != null) {
      return datasetBuilders;
    }
    return new ArrayList<>(0);
  }

  public boolean hasService(String name) {
    if (services == null) {
      return false;
    }
    for (Service s : services) {
      if (s.getName().equalsIgnoreCase(name)) {
        return true;
      }
    }
    return false;
  }

  public boolean hasServiceInDataset(String name) {
    for (DatasetBuilder dsb : datasetBuilders) {
      for (Service s : dsb.getServices()) {
        if (s.getName().equalsIgnoreCase(name)) {
          return true;
        }
      }
    }
    return false;
  }



  /////////////////////////////////////////////////////////////////////
  // JDOM

  private void readXML(String location) {
    try {
      SAXBuilder saxBuilder = new SAXBuilder();
      saxBuilder.setExpandEntities(false);
      Document jdomDoc = saxBuilder.build(location);
      readCatalog(jdomDoc.getRootElement());
    } catch (Exception e) {
      logError(e, "failed to read xml catalog at " + location);
    }
  }

  private void readXML(URI uri) {
    try {
      SAXBuilder saxBuilder = new SAXBuilder();
      saxBuilder.setExpandEntities(false);
      Document jdomDoc = saxBuilder.build(uri.toURL());
      readCatalog(jdomDoc.getRootElement());
    } catch (Exception e) {
      logError(e, "failed to read xml catalog at " + uri);
    }
  }

  private void readXMLfromString(String catalogAsString) {
    try {
      StringReader in = new StringReader(catalogAsString);
      SAXBuilder saxBuilder = new SAXBuilder(); // LOOK non-validating
      saxBuilder.setExpandEntities(false);
      Document jdomDoc = saxBuilder.build(in);
      readCatalog(jdomDoc.getRootElement());
    } catch (Exception e) {
      logError(e, "failed to read xml catalog at " + baseURI);
    }
  }

  private void readXML(InputStream stream) {
    try {
      SAXBuilder saxBuilder = new SAXBuilder();
      saxBuilder.setExpandEntities(false);
      Document jdomDoc = saxBuilder.build(stream);
      readCatalog(jdomDoc.getRootElement());
    } catch (Exception e) {
      logError(e, "failed to read xml catalog at " + baseURI);
    }
  }

  private void logError(Exception e, String message) {
    errlog.format(message);
    logger.error(message + ", err=" + e);
    if (logger.isTraceEnabled()) {
      e.printStackTrace();
    }
    fatalError = true;
  }

  /*
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   */
  private void readCatalog(Element catalogElem) {

    String name = catalogElem.getAttributeValue("name");
    String catSpecifiedBaseURL = catalogElem.getAttributeValue("base"); // LOOK what is this ??
    String expiresS = catalogElem.getAttributeValue("expires");
    String version = catalogElem.getAttributeValue("version");

    CalendarDate expires = null;
    if (expiresS != null) {
      try {
        expires = CalendarDateFormatter.isoStringToCalendarDate(null, expiresS);
      } catch (Exception e) {
        errlog.format("bad expires date '%s' err='%s'%n", expiresS, e.getMessage());
        logger.debug("bad expires date '{}' err='{}'%n", expiresS, e.getMessage());
      }
    }

    if (catSpecifiedBaseURL != null) {
      try {
        URI userSpecifiedBaseUri = new URI(catSpecifiedBaseURL);
        this.baseURI = userSpecifiedBaseUri;
      } catch (URISyntaxException e) {
        errlog.format("readCatalog(): bad catalog specified base URI='%s' %n", catSpecifiedBaseURL);
        logger.debug("bad catalog specified base URI='{}'", catSpecifiedBaseURL);
      }
    }

    setName(name);
    setExpires(expires);
    setVersion(version);

    // read top-level services
    java.util.List sList = catalogElem.getChildren("service", Catalog.defNS);
    for (Element e : sList) {
      addService(readService(e));
    }

    // read top-level properties
    java.util.List pList = catalogElem.getChildren("property", Catalog.defNS);
    for (Element e : pList) {
      addProperty(readProperty(e));
    }

    // look for top-level dataset and catalogRefs elements (keep them in order)
    java.util.List allChildren = catalogElem.getChildren();
    for (Element e : allChildren) {
      if (e.getName().equals("dataset")) {
        addDataset(readDataset(null, e));
      } else if (e.getName().equals("catalogRef")) {
        addDataset(readCatalogRef(null, e));
      } else {
        addDataset(buildOtherDataset(null, e));
      }
    }
  }

  // for overridding
  protected DatasetBuilder buildOtherDataset(DatasetBuilder parent, Element dsElem) {
    return null;
  }

  /*
   * 
   * 
   * 
   *  // whyd we do that ?
   * 
   * 
   * 
   * 
   * 
   * 
   */
  protected AccessBuilder readAccess(DatasetBuilder dataset, Element accessElem) {
    String urlPath = accessElem.getAttributeValue("urlPath");
    String serviceName = accessElem.getAttributeValue("serviceName");
    String dataFormat = accessElem.getAttributeValue("dataFormat");

    Service s = serviceMap.get(serviceName);
    if (s == null) {
      errlog.format("Cant find service name='%s'%n", serviceName);
      logger.debug("Can't find service name='{}'", serviceName);
    }
    return new AccessBuilder(dataset, urlPath, s, dataFormat, readDataSize(accessElem));
  }

  protected Property readProperty(Element s) {
    String name = s.getAttributeValue("name");
    String value = s.getAttributeValue("value");
    return new Property(name, value);
  }

  /*
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   */
  protected Service readService(Element s) {
    String name = s.getAttributeValue("name");
    String typeS = s.getAttributeValue("serviceType");
    String serviceBase = s.getAttributeValue("base");
    String suffix = s.getAttributeValue("suffix");
    String desc = s.getAttributeValue("desc");
    String accessType = s.getAttributeValue("accessType");

    ServiceType type = ServiceType.getServiceTypeIgnoreCase(typeS);
    if (type == null) {
      errlog.format(" non-standard service type = '%s'%n", typeS);
      logger.debug(" non-standard service type = '{}'", typeS);
    } else {
      if (desc == null) {
        desc = type.getDescription();
      }
      if (accessType == null) {
        accessType = type.getAccessType();
      }
    }

    List properties = null;
    List propertyList = s.getChildren("property", Catalog.defNS);
    for (Element e : propertyList) {
      if (properties == null) {
        properties = new ArrayList<>();
      }
      properties.add(readProperty(e));
    }

    // nested services
    List services = null;
    java.util.List serviceList = s.getChildren("service", Catalog.defNS);
    for (Element e : serviceList) {
      if (services == null) {
        services = new ArrayList<>();
      }
      services.add(readService(e));
    }

    Service result = new Service(name, serviceBase, typeS, desc, suffix, services, properties, accessType);
    serviceMap.put(name, result);
    return result;
  }


  /*
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   */
  protected DatasetBuilder readCatalogRef(DatasetBuilder parent, Element catRefElem) {
    String title = catRefElem.getAttributeValue("title", Catalog.xlinkNS);
    if (title == null) {
      title = catRefElem.getAttributeValue("name");
    }
    String href = catRefElem.getAttributeValue("href", Catalog.xlinkNS);
    CatalogRefBuilder catRef = new CatalogRefBuilder(parent);
    readDatasetInfo(catRef, catRefElem);
    catRef.setTitle(title);
    catRef.setHref(href);
    return catRef;
  }

  /*
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   *  
   * 
   *  
   * 
   * 
   * 
   * 
   *  
   * 
   * 
   */
  protected DatasetBuilder readDataset(DatasetBuilder parent, Element dsElem) {

    DatasetBuilder dataset = new DatasetBuilder(parent);
    readDatasetInfo(dataset, dsElem);

    // look for access elements
    java.util.List aList = dsElem.getChildren("access", Catalog.defNS);
    for (Element e : aList) {
      dataset.addAccess(readAccess(dataset, e));
    }

    // look for nested dataset and catalogRefs elements (keep them in order)
    java.util.List allChildren = dsElem.getChildren();
    for (Element e : allChildren) {
      if (e.getName().equals("dataset")) {
        dataset.addDataset(readDataset(dataset, e));
      } else if (e.getName().equals("catalogRef")) {
        dataset.addDataset(readCatalogRef(dataset, e));
      } else {
        dataset.addDataset(buildOtherDataset(dataset, e));
      }
    }

    return dataset;
  }

  protected void readDatasetInfo(DatasetBuilder dataset, Element dsElem) {
    // read attributes
    String name = dsElem.getAttributeValue("name");
    if (name == null) {
      if (dsElem.getName().equals("catalogRef")) {
        dataset.setName("");
      } else {
        errlog.format(" ** warning: dataset must have a name = '%s'%n", dsElem);
        logger.debug(" ** warning: dataset must have a name = '{}'", dsElem);
      }
    } else {
      dataset.setName(name);
    }

    dataset.put(Dataset.Alias, dsElem.getAttributeValue("alias"));
    dataset.put(Dataset.Authority, dsElem.getAttributeValue("authority"));
    dataset.put(Dataset.CollectionType, dsElem.getAttributeValue("collectionType"));
    dataset.put(Dataset.Id, dsElem.getAttributeValue("ID"));
    dataset.putInheritedField(Dataset.RestrictAccess, dsElem.getAttributeValue("restrictAccess"));
    dataset.put(Dataset.ServiceName, dsElem.getAttributeValue("serviceName"));
    dataset.put(Dataset.UrlPath, dsElem.getAttributeValue("urlPath"));

    String dataTypeName = dsElem.getAttributeValue("dataType");
    dataset.put(Dataset.FeatureType, dataTypeName);
    if (dataTypeName != null) {
      FeatureType dataType = FeatureType.getType(dataTypeName.toUpperCase());
      if (dataType == null) {
        errlog.format(" ** warning: non-standard data type = '%s'%n", dataTypeName);
        logger.debug(" ** warning: non-standard data type = '{}'", dataTypeName);
      }
    }

    String harvest = dsElem.getAttributeValue("harvest");
    if ("true".equalsIgnoreCase(harvest)) {
      dataset.put(Dataset.Harvest, Boolean.TRUE);
    }

    // catalog.addDatasetByID(dataset); // LOOK need to do immed for alias processing

    // read elements
    readThreddsMetadataGroup(dataset.flds, dataset, dsElem);
  }

  /*
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   */
  protected void readThreddsMetadataGroup(Map flds, DatasetBuilder dataset, Element parent) {
    List list;

    // look for creators - kind of a Source
    list = parent.getChildren("creator", Catalog.defNS);
    for (Element e : list) {
      DatasetBuilder.addToList(flds, Dataset.Creators, readSource(e));
    }

    // look for contributors
    list = parent.getChildren("contributor", Catalog.defNS);
    for (Element e : list) {
      DatasetBuilder.addToList(flds, Dataset.Contributors, readContributor(e));
    }

    // look for dates
    list = parent.getChildren("date", Catalog.defNS);
    for (Element e : list) {
      DatasetBuilder.addToList(flds, Dataset.Dates, readDate(e, null));
    }

    // look for documentation
    list = parent.getChildren("documentation", Catalog.defNS);
    for (Element e : list) {
      DatasetBuilder.addToList(flds, Dataset.Documentation, readDocumentation(e));
    }

    // look for keywords - kind of a controlled vocabulary
    list = parent.getChildren("keyword", Catalog.defNS);
    for (Element e : list) {
      DatasetBuilder.addToList(flds, Dataset.Keywords, readControlledVocabulary(e));
    }

    // look for metadata elements
    list = parent.getChildren("metadata", Catalog.defNS);
    for (Element e : list) {
      DatasetBuilder.addToList(flds, Dataset.MetadataOther, readMetadata(flds, dataset, e));
    }

    // look for projects - kind of a controlled vocabulary
    list = parent.getChildren("project", Catalog.defNS);
    for (Element e : list) {
      DatasetBuilder.addToList(flds, Dataset.Projects, readControlledVocabulary(e));
    }

    // look for properties
    list = parent.getChildren("property", Catalog.defNS);
    for (Element e : list) {
      DatasetBuilder.addToList(flds, Dataset.Properties, readProperty(e));
    }

    // look for publishers - kind of a Source
    list = parent.getChildren("publisher", Catalog.defNS);
    for (Element e : list) {
      DatasetBuilder.addToList(flds, Dataset.Publishers, readSource(e));
    }

    // look for variables
    list = parent.getChildren("variables", Catalog.defNS);
    for (Element e : list) {
      DatasetBuilder.addToList(flds, Dataset.VariableGroups, readVariables(e));
    }

    // can only be one each of these kinds
    ThreddsMetadata.GeospatialCoverage gc =
        readGeospatialCoverage(parent.getChild("geospatialCoverage", Catalog.defNS));
    if (gc != null) {
      flds.put(Dataset.GeospatialCoverage, gc);
    }

    DateRange tc = readTimeCoverage(parent.getChild("timeCoverage", Catalog.defNS));
    if (tc != null) {
      flds.put(Dataset.TimeCoverage, tc);
    }

    Element serviceNameElem = parent.getChild("serviceName", Catalog.defNS);
    if (serviceNameElem != null) {
      flds.put(Dataset.ServiceName, serviceNameElem.getText());
    }

    Element authElem = parent.getChild("authority", Catalog.defNS);
    if (authElem != null) {
      flds.put(Dataset.Authority, authElem.getText());
    }

    Element dataTypeElem = parent.getChild("dataType", Catalog.defNS);
    if (dataTypeElem != null) {
      String dataTypeName = dataTypeElem.getText();
      flds.put(Dataset.FeatureType, dataTypeName);
      if ((dataTypeName != null) && (!dataTypeName.isEmpty())) {
        FeatureType dataType = FeatureType.getType(dataTypeName.toUpperCase());
        if (dataType == null) {
          errlog.format(" ** warning: non-standard feature type = '%s'%n", dataTypeName);
          logger.debug(" ** warning: non-standard feature type = '{}'", dataTypeName);
        }
      }
    }

    Element dataFormatElem = parent.getChild("dataFormat", Catalog.defNS);
    if (dataFormatElem != null) {
      String dataFormatTypeName = dataFormatElem.getText();
      if ((dataFormatTypeName != null) && (!dataFormatTypeName.isEmpty())) {
        DataFormatType dataFormatType = DataFormatType.getType(dataFormatTypeName);
        if (dataFormatType == null) {
          errlog.format(" ** warning: non-standard dataFormat type = '%s'%n", dataFormatTypeName);
          logger.debug(" ** warning: non-standard dataFormat type = '{}'", dataFormatTypeName);
        }
        flds.put(Dataset.DataFormatType, dataFormatTypeName);
      }
    }

    long size = readDataSize(parent);
    if (size > 0) {
      flds.put(Dataset.DataSize, size);
    }

    // LOOK: we seem to have put a variableMap element not contained by 
    ThreddsMetadata.UriResolved mapUri = readUri(parent.getChild("variableMap", Catalog.defNS), "variableMap");
    if (mapUri != null) {
      flds.put(Dataset.VariableMapLinkURI, mapUri);
    }
  }


  protected ThreddsMetadata.Contributor readContributor(Element elem) {
    if (elem == null) {
      return null;
    }
    return new ThreddsMetadata.Contributor(elem.getText(), elem.getAttributeValue("role"));
  }

  protected long readDataSize(Element parent) {
    Element elem = parent.getChild("dataSize", Catalog.defNS);
    if (elem == null) {
      return -1;
    }

    double size;
    String sizeS = elem.getText();
    try {
      size = Double.parseDouble(sizeS);
    } catch (NumberFormatException e) {
      errlog.format(" ** Parse error: Bad double format in size element = '%s'%n", sizeS);
      logger.debug(" ** Parse error: Bad double format in size element = '{}'", sizeS);
      return -1;
    }

    String units = elem.getAttributeValue("units");
    char c = Character.toUpperCase(units.charAt(0));
    if (c == 'K') {
      size *= 1000;
    } else if (c == 'M') {
      size *= 1000 * 1000;
    } else if (c == 'G') {
      size *= 1000 * 1000 * 1000;
    } else if (c == 'T') {
      size *= 1000.0 * 1000 * 1000 * 1000;
    } else if (c == 'P') {
      size *= 1000.0 * 1000 * 1000 * 1000 * 1000;
    }
    return (long) size;
  }

  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


  protected Documentation readDocumentation(Element s) {
    String href = s.getAttributeValue("href", Catalog.xlinkNS);
    String title = s.getAttributeValue("title", Catalog.xlinkNS);
    String type = s.getAttributeValue("type"); // not XLink type
    String content = s.getTextNormalize();

    URI uri = null;
    if (href != null) {
      try {
        uri = Catalog.resolveUri(baseURI, href);
      } catch (Exception e) {
        errlog.format(" ** Invalid documentation href = '%s' err='%s'%n", href, e.getMessage());
        logger.debug(" ** Invalid documentation href = '{}' err='{}'", href, e.getMessage());
      }
    }

    return new Documentation(href, uri, title, type, content);
  }

  protected double readDouble(Element elem) {
    if (elem == null) {
      return Double.NaN;
    }
    String text = elem.getText();
    try {
      return Double.parseDouble(text);
    } catch (NumberFormatException e) {
      errlog.format(" ** Parse error: Bad double format = '%s'%n", text);
      logger.debug(" ** Parse error: Bad double format = '{}'", text);
      return Double.NaN;
    }
  }

  /*
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   */
  protected ThreddsMetadata.GeospatialCoverage readGeospatialCoverage(Element gcElem) {
    if (gcElem == null) {
      return null;
    }

    String zpositive = gcElem.getAttributeValue("zpositive");

    ThreddsMetadata.GeospatialRange northsouth =
        readGeospatialRange(gcElem.getChild("northsouth", Catalog.defNS), CDM.LAT_UNITS);
    ThreddsMetadata.GeospatialRange eastwest =
        readGeospatialRange(gcElem.getChild("eastwest", Catalog.defNS), CDM.LON_UNITS);
    ThreddsMetadata.GeospatialRange updown = readGeospatialRange(gcElem.getChild("updown", Catalog.defNS), "m");

    // look for names
    List names = new ArrayList<>();
    java.util.List list = gcElem.getChildren("name", Catalog.defNS);
    for (Element e : list) {
      ThreddsMetadata.Vocab name = readControlledVocabulary(e);
      names.add(name);
    }

    return new ThreddsMetadata.GeospatialCoverage(eastwest, northsouth, updown, names, zpositive);
  }

  protected ThreddsMetadata.GeospatialRange readGeospatialRange(Element spElem, String defUnits) {
    if (spElem == null) {
      return null;
    }

    double start = readDouble(spElem.getChild("start", Catalog.defNS));
    double size = readDouble(spElem.getChild("size", Catalog.defNS));
    double resolution = readDouble(spElem.getChild("resolution", Catalog.defNS));

    String units = spElem.getChildText("units", Catalog.defNS);
    if (units == null) {
      units = defUnits;
    }

    return new ThreddsMetadata.GeospatialRange(start, size, resolution, units);
  }

  /*
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   */
  protected ThreddsMetadata.MetadataOther readMetadata(Map flds, DatasetBuilder dataset,
      Element mdataElement) {
    // there are 6 cases to deal with: threddsNamespace vs not & inline vs Xlink & (if thredds) inherited or not
    Namespace namespace;
    List inlineElements = mdataElement.getChildren();
    if (!inlineElements.isEmpty()) {
      // look at the namespace of the children, if they exist
      namespace = ((Element) inlineElements.get(0)).getNamespace();
    } else {
      namespace = mdataElement.getNamespace(); // will be thredds
    }

    String mtype = mdataElement.getAttributeValue("metadataType");
    String href = mdataElement.getAttributeValue("href", Catalog.xlinkNS);
    String title = mdataElement.getAttributeValue("title", Catalog.xlinkNS);
    String inheritedS = mdataElement.getAttributeValue("inherited");
    boolean inherited = "true".equalsIgnoreCase(inheritedS);

    boolean isThreddsNamespace = ((mtype == null) || mtype.equalsIgnoreCase("THREDDS"))
        && namespace.getURI().equals(Catalog.CATALOG_NAMESPACE_10);

    // the case where its not ThreddsMetadata
    if (!isThreddsNamespace) {
      if (!inlineElements.isEmpty()) {
        // just hold onto the jdom elements as the "content"
        return new ThreddsMetadata.MetadataOther(mtype, namespace.getURI(), namespace.getPrefix(), inherited,
            mdataElement);

      } else {
        // otherwise it must be an Xlink
        return new ThreddsMetadata.MetadataOther(href, title, mtype, namespace.getURI(), namespace.getPrefix(),
            inherited);
      }
    }

    // the case where its ThreddsMetadata
    Map useFlds;
    if (inherited) {
      // the case where its inherited ThreddsMetadata: gonna put stuff in the tmi.
      ThreddsMetadata tmi = (ThreddsMetadata) dataset.get(Dataset.ThreddsMetadataInheritable);
      if (tmi == null) {
        tmi = new ThreddsMetadata();
        dataset.put(Dataset.ThreddsMetadataInheritable, tmi);
      }
      useFlds = tmi.getFlds();

    } else {
      // the case where its non-inherited ThreddsMetadata: gonna put stuff directly into the dataset
      useFlds = flds;
    }
    readThreddsMetadataGroup(useFlds, dataset, mdataElement);

    // also need to capture any XLinks. see
    // http://www.unidata.ucar.edu/software/thredds/v4.6/tds/catalog/InvCatalogSpec.html#metadataElement
    // in this case we just suck it in as if it was inline
    if (href != null) {
      try {
        URI xlinkUri = Catalog.resolveUri(baseURI, href);
        Element remoteMdata = readMetadataFromUrl(xlinkUri);
        return readMetadata(useFlds, dataset, remoteMdata);
      } catch (Exception ioe) {
        errlog.format("Cant read in referenced metadata %s err=%s%n", href, ioe.getMessage());
        logger.debug("Can't read in referenced metadata {} err={}}", href, ioe.getMessage());
      }

    }
    return null; // ThreddsMetadata.MetadataOther was directly added
  }

  private Element readMetadataFromUrl(java.net.URI uri) throws java.io.IOException {
    SAXBuilder saxBuilder = new SAXBuilder();
    saxBuilder.setExpandEntities(false);
    Document doc;
    try {
      doc = saxBuilder.build(uri.toURL());
    } catch (Exception e) {
      throw new IOException(e.getMessage());
    }
    return doc.getRootElement();
  }

  /*
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   */
  protected ThreddsMetadata.Source readSource(Element elem) {
    if (elem == null) {
      return null;
    }
    ThreddsMetadata.Vocab name = readControlledVocabulary(elem.getChild("name", Catalog.defNS));
    Element contact = elem.getChild("contact", Catalog.defNS);
    if (contact == null) {
      errlog.format(" ** Parse error: Missing contact element in = '%s'%n", elem.getName());
      logger.debug(" ** Parse error: Missing contact element in = '{}'", elem.getName());
      return null;
    }
    return new ThreddsMetadata.Source(name, contact.getAttributeValue("url"), contact.getAttributeValue("email"));
  }

  /*
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * Must conform to complete udunits date string, eg "20 days since 1991-01-01"
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * Must conform to udunits time duration, eg "20.1 hours"
   * 
   * 
   * 
   */
  protected DateRange readTimeCoverage(Element tElem) {
    if (tElem == null) {
      return null;
    }

    Calendar calendar = readCalendar(tElem.getAttributeValue("calendar"));
    DateType start = readDate(tElem.getChild("start", Catalog.defNS), calendar);
    DateType end = readDate(tElem.getChild("end", Catalog.defNS), calendar);

    TimeDuration duration = readDuration(tElem.getChild("duration", Catalog.defNS));
    TimeDuration resolution = readDuration(tElem.getChild("resolution", Catalog.defNS));

    try {
      return new DateRange(start, end, duration, resolution);
    } catch (java.lang.IllegalArgumentException e) {
      errlog.format(" ** warning: TimeCoverage error ='%s'%n", e.getMessage());
      logger.debug(" ** warning: TimeCoverage error ='{}'", e.getMessage());
      return null;
    }
  }

  protected Calendar readCalendar(String calendarAttribValue) {
    if (calendarAttribValue == null) {
      return Calendar.getDefault();
    }

    Calendar calendar = Calendar.get(calendarAttribValue);
    if (calendar == null) {
      errlog.format(" ** Parse error: Bad calendar name = '%s'%n", calendarAttribValue);
      logger.debug(" ** Parse error: Bad calendar name = '{}}'", calendarAttribValue);
      return Calendar.getDefault();
    }

    return calendar;
  }

  protected DateType readDate(Element elem, Calendar calendar) {
    if (elem == null) {
      return null;
    }
    String format = elem.getAttributeValue("format");
    String type = elem.getAttributeValue("type");
    return makeDateType(elem.getText(), format, type, calendar);
  }

  protected DateType makeDateType(String text, String format, String type, Calendar calendar) {
    if (text == null) {
      return null;
    }
    try {
      return new DateType(text, format, type, calendar);
    } catch (java.text.ParseException e) {
      errlog.format(" ** Parse error: Bad date format = '%s'%n", text);
      return null;
    }
  }

  protected TimeDuration readDuration(Element elem) {
    if (elem == null) {
      return null;
    }
    String text = null;
    try {
      text = elem.getText();
      return new TimeDuration(text);
    } catch (java.text.ParseException e) {
      errlog.format(" ** Parse error: Bad duration format = '%s'%n", text);
      return null;
    }
  }

  /*
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   * 
   */
  protected ThreddsMetadata.VariableGroup readVariables(Element varsElem) {
    if (varsElem == null) {
      return null;
    }

    String vocab = varsElem.getAttributeValue("vocabulary");
    ThreddsMetadata.UriResolved variableVocabUri = readUri(varsElem, "Variables vocabulary");

    java.util.List vlist = varsElem.getChildren("variable", Catalog.defNS);
    ThreddsMetadata.UriResolved variableMap = readUri(varsElem.getChild("variableMap", Catalog.defNS), "Variables Map");
    if ((variableMap != null) && !vlist.isEmpty()) { // cant do both
      errlog.format(" ** Catalog error: cant have variableMap and variable in same element '%s'%n", varsElem);
    }

    List variables = new ArrayList<>();
    for (Element e : vlist) {
      variables.add(readVariable(e));
    }

    return new ThreddsMetadata.VariableGroup(vocab, variableVocabUri, variableMap, variables);
  }

  public static ThreddsMetadata.Variable readVariable(Element varElem) {
    if (varElem == null) {
      return null;
    }

    String name = varElem.getAttributeValue("name");
    String desc = varElem.getText();
    String vocabulary_name = varElem.getAttributeValue("vocabulary_name");
    String units = varElem.getAttributeValue("units");
    String id = varElem.getAttributeValue("vocabulary_id");

    return new ThreddsMetadata.Variable(name, desc, vocabulary_name, units, id);
  }

  protected ThreddsMetadata.Vocab readControlledVocabulary(Element elem) {
    if (elem == null) {
      return null;
    }
    return new ThreddsMetadata.Vocab(elem.getText(), elem.getAttributeValue("vocabulary"));
  }

  private ThreddsMetadata.UriResolved readUri(Element elemWithHref, String what) {
    if (elemWithHref == null) {
      return null;
    }
    String mapHref = elemWithHref.getAttributeValue("href", Catalog.xlinkNS);
    if (mapHref == null) {
      return null;
    }

    try {
      String mapUri = URLnaming.resolve(baseURI.toString(), mapHref);
      return new ThreddsMetadata.UriResolved(mapHref, new URI(mapUri));
    } catch (Exception e) {
      errlog.format(" ** Invalid %s URI= '%s' err='%s'%n", what, mapHref, e.getMessage());
      return null;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy