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

ucar.nc2.thredds.ThreddsDataFactory Maven / Gradle / Ivy

Go to download

The NetCDF-Java Library is a Java interface to NetCDF files, as well as to many other types of scientific data formats.

The newest version!
/*
 * Copyright 1998-2009 University Corporation for Atmospheric Research/Unidata
 *
 * Portions of this software were developed by the Unidata Program at the
 * University Corporation for Atmospheric Research.
 *
 * Access and use of this software shall impose the following obligations
 * and understandings on the user. The user is granted the right, without
 * any fee or cost, to use, copy, modify, alter, enhance and distribute
 * this software, and any derivative works thereof, and its supporting
 * documentation for any purpose whatsoever, provided that this entire
 * notice appears in all copies of the software, derivative works and
 * supporting documentation.  Further, UCAR requests that the user credit
 * UCAR/Unidata in any publications that result from the use of this
 * software or in any product that includes this software. The names UCAR
 * and/or Unidata, however, may not be used in any advertising or publicity
 * to endorse or promote any products or commercial entity unless specific
 * written permission is obtained from UCAR/Unidata. The user also
 * understands that UCAR/Unidata is not obligated to provide the user with
 * any support, consulting, training or assistance of any kind with regard
 * to the use, operation and performance of this software nor to provide
 * the user with any updates, revisions, new versions or "bug fixes."
 *
 * THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
 */
package ucar.nc2.thredds;

import ucar.nc2.*;
import ucar.nc2.stream.CdmRemote;
import ucar.nc2.stream.CdmrFeatureDataset;
import ucar.nc2.ft.FeatureDatasetFactoryManager;
import ucar.nc2.constants.FeatureType;

import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.ncml.NcMLReader;

import thredds.catalog.*;
import ucar.unidata.util.StringUtil2;

import java.util.List;
import java.util.ArrayList;
import java.util.Formatter;
import java.io.IOException;

/**
 * This tries to translate a THREDDS InvDataset into a data object that can be used, either as a NetcdfDataset or as a FeatureDataset.
 * 

* As input, it can take *

  1. An InvAccess object. *
  2. An InvDataset object. If the InvDataset has more that one InvAccess, it has to try to choose which to use, * based on what Service type we know how to work with. *
  3. A url of the form [thredds:]catalog.xml#datasetId. In this case it opens the catalog, and looks for the * InvDataset with the given datasetId. *
  4. A url of the form thredds:resolve:resolveURL. In this case it expects that the URL will return a catalog with a * single top level dataset, which is the "resolved" dataset. *
*

* It annotates the NetcdfDataset with info from the InvDataset. *

* You can reuse a ThreddsDataFactory, but only within a single thread. * * @author caron */ public class ThreddsDataFactory { static public final String PROTOCOL = "thredds"; static public final String SCHEME = PROTOCOL + ":"; static private boolean preferCdm = true; static public void setPreferCdm(boolean prefer) { preferCdm = prefer; } static public void setDebugFlags(ucar.nc2.util.DebugFlags debugFlag) { debugOpen = debugFlag.isSet("thredds/debugOpen"); debugTypeOpen = debugFlag.isSet("thredds/openDatatype"); } static private boolean debugOpen = false; static private boolean debugTypeOpen = false; /** * The result of trying to open a THREDDS dataset. * If fatalError is true, the operation failed, errLog should indicate why. * Otherwise, the FeatureType and FeatureDataset is valid. * There may still be warning or diagnostic errors in errLog. */ public static class Result { public boolean fatalError; public Formatter errLog = new Formatter(); public FeatureType featureType; public ucar.nc2.ft.FeatureDataset featureDataset; public String imageURL; public String location; public InvAccess accessUsed; @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("Result"); sb.append("{fatalError=").append(fatalError); sb.append(", errLog=").append(errLog); sb.append(", featureType=").append(featureType); sb.append(", featureDataset=").append(featureDataset); sb.append(", imageURL='").append(imageURL).append('\''); sb.append(", location='").append(location).append('\''); sb.append(", accessUsed=").append(accessUsed); sb.append('}'); return sb.toString(); } } /** * Open a FeatureDataset from a URL location string. Example URLS:

    *
  • http://localhost:8080/test/addeStationDataset.xml#surfaceHourly *
  • thredds:http://localhost:8080/test/addeStationDataset.xml#surfaceHourly *
  • thredds://localhost:8080/test/addeStationDataset.xml#surfaceHourly *
  • thredds:file:c:/test/data/catalog/addeStationDataset.xml#AddeSurfaceData (absolute file) *
  • thredds:resolve:resolveURL *
* * @param urlString [thredds:]catalog.xml#datasetId * @param task may be null * @return ThreddsDataFactory.Result check fatalError for validity * @throws java.io.IOException on read error */ public ThreddsDataFactory.Result openFeatureDataset(String urlString, ucar.nc2.util.CancelTask task) throws IOException { ThreddsDataFactory.Result result = new ThreddsDataFactory.Result(); InvDataset invDataset = processLocation(urlString, task, result); if (result.fatalError) return result; return openFeatureDataset(null, invDataset, task, result); } /** * Open a FeatureDataset from a URL location string, and a desired type (may by NONE or null). * * @param wantFeatureType desired feature type, may be NONE or null * @param urlString [thredds:]catalog.xml#datasetId * @param task may be null * @return ThreddsDataFactory.Result check fatalError for validity * @throws java.io.IOException on read error */ public ThreddsDataFactory.Result openFeatureDataset(FeatureType wantFeatureType, String urlString, ucar.nc2.util.CancelTask task) throws IOException { ThreddsDataFactory.Result result = new ThreddsDataFactory.Result(); InvDataset invDataset = processLocation(urlString, task, result); if (result.fatalError || invDataset == null) return result; return openFeatureDataset(wantFeatureType, invDataset, task, result); } private InvDataset processLocation(String location, ucar.nc2.util.CancelTask task, Result result) { location = location.trim(); location = StringUtil2.replace(location, '\\', "/"); if (location.startsWith(SCHEME)) location = location.substring(8); if (location.startsWith("resolve:")) { location = location.substring(8); return openResolver(location, task, result); } if (!location.startsWith("http:") && !location.startsWith("file:")) // LOOK whats this for?? location = "http:" + location; InvCatalog catalog; InvDataset invDataset; String datasetId; int pos = location.indexOf('#'); if (pos < 0) { result.fatalError = true; result.errLog.format("Must have the form catalog.xml#datasetId%n"); return null; } InvCatalogFactory catFactory = new InvCatalogFactory("", false); String catalogLocation = location.substring(0, pos); catalog = catFactory.readXML(catalogLocation); StringBuilder buff = new StringBuilder(); if (!catalog.check(buff)) { result.errLog.format("Invalid catalog from Resolver <%s>%n%s%n", catalogLocation, buff.toString()); result.fatalError = true; return null; } datasetId = location.substring(pos + 1); invDataset = catalog.findDatasetByID(datasetId); if (invDataset == null) { result.fatalError = true; result.errLog.format("Could not find dataset %s in %s %n", datasetId, catalogLocation); return null; } return invDataset; } /** * Open a FeatureDataset from an InvDataset object, deciding on which InvAccess to use. * * @param invDataset use this to figure out what type, how to open, etc * @param task allow user to cancel; may be null * @return ThreddsDataFactory.Result check fatalError for validity * @throws IOException on read error */ public ThreddsDataFactory.Result openFeatureDataset(InvDataset invDataset, ucar.nc2.util.CancelTask task) throws IOException { return openFeatureDataset(null, invDataset, task, new Result()); } public ThreddsDataFactory.Result openFeatureDataset(FeatureType wantFeatureType, InvDataset invDataset, ucar.nc2.util.CancelTask task, Result result) throws IOException { result.featureType = invDataset.getDataType(); if (result.featureType == null) result.featureType = wantFeatureType; // look for remote FeatureDataset if ((result.featureType != null) && result.featureType.isPointFeatureType()) { InvAccess access = findAccessByServiceType(invDataset.getAccess(), ServiceType.CdmrFeature); if (access != null) return openFeatureDataset(result.featureType, access, task, result); } // special handling for images if (result.featureType == FeatureType.IMAGE) { InvAccess access = getImageAccess(invDataset, task, result); if (access != null) { return openFeatureDataset(result.featureType, access, task, result); } else result.fatalError = true; return result; } // special handling for DQC InvAccess qc = invDataset.getAccess(ServiceType.QC); if (qc != null) { if (result.featureType == FeatureType.STATION) { /* DqcFactory dqcFactory = new DqcFactory(true); String dqc_location = qc.getStandardUrlName(); QueryCapability dqc = dqcFactory.readXML(dqc_location); if (dqc.hasFatalError()) { result.errLog.append(dqc.getErrorMessages()); result.fatalError = true; } */ result.featureDataset = null; // LOOK FIX ucar.nc2.thredds.DqcStationObsDataset.factory(invDataset, dqc_location, result.errLog); result.fatalError = (result.featureDataset == null); } else { result.errLog.format("DQC must be station DQC, dataset = %s %n", invDataset.getName()); result.fatalError = true; } return result; } NetcdfDataset ncd = openDataset(invDataset, true, task, result); if (null != ncd) result.featureDataset = FeatureDatasetFactoryManager.wrap(result.featureType, ncd, task, result.errLog); if (null == result.featureDataset) result.fatalError = true; else { result.location = result.featureDataset.getLocation(); if ((result.featureType == null) && (result.featureDataset != null)) result.featureType = result.featureDataset.getFeatureType(); } return result; } /** * Open a FeatureDataset from an InvAccess object. * * @param access use this InvAccess. * @param task may be null * @return ThreddsDataFactory.Result check fatalError for validity * @throws IOException on read error */ public ThreddsDataFactory.Result openFeatureDataset(InvAccess access, ucar.nc2.util.CancelTask task) throws IOException { InvDataset invDataset = access.getDataset(); ThreddsDataFactory.Result result = new Result(); if (invDataset.getDataType() == null) { result.errLog.format("InvDatasert must specify a FeatureType%n"); result.fatalError = true; return result; } return openFeatureDataset(invDataset.getDataType(), access, task, result); } private ThreddsDataFactory.Result openFeatureDataset(FeatureType wantFeatureType, InvAccess access, ucar.nc2.util.CancelTask task, Result result) throws IOException { result.featureType = wantFeatureType; result.accessUsed = access; // special handling for IMAGE if (result.featureType == FeatureType.IMAGE) { result.imageURL = access.getStandardUrlName(); result.location = result.imageURL; return result; } if (access.getService().getServiceType() == ServiceType.CdmrFeature) { result.featureDataset = CdmrFeatureDataset.factory(wantFeatureType, access.getStandardUrlName()); } else { // all other datatypes NetcdfDataset ncd = openDataset(access, true, task, result); if (null != ncd) { result.featureDataset = FeatureDatasetFactoryManager.wrap(result.featureType, ncd, task, result.errLog); } } if (null == result.featureDataset) result.fatalError = true; else { result.location = result.featureDataset.getLocation(); if ((result.featureType == null) && (result.featureDataset != null)) result.featureType = result.featureDataset.getFeatureType(); } return result; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Open a NetcdfDataset from a URL location string. Example URLS:
    *
  • http://localhost:8080/test/addeStationDataset.xml#surfaceHourly *
  • thredds:http://localhost:8080/test/addeStationDataset.xml#surfaceHourly *
  • thredds://localhost:8080/test/addeStationDataset.xml#surfaceHourly *
  • thredds:file:c:/dev/netcdf-java-2.2/test/data/catalog/addeStationDataset.xml#AddeSurfaceData (absolute file) *
  • thredds:resolve:resolveURL *
* * @param location catalog.xml#datasetId, may optionally start with "thredds:" * @param task may be null * @param log error messages gp here, may be null * @param acquire if true, aquire the dataset, else open it * @return NetcdfDataset * @throws java.io.IOException on read error */ public NetcdfDataset openDataset(String location, boolean acquire, ucar.nc2.util.CancelTask task, Formatter log) throws IOException { Result result = new Result(); InvDataset invDataset = processLocation(location, task, result); if (result.fatalError || invDataset == null) { if (log != null) log.format("%s", result.errLog); return null; } return openDataset(invDataset, acquire, task, result); } /** * Try to open as a NetcdfDataset. * * @param invDataset open this * @param acquire if true, aquire the dataset, else open it * @param task may be null * @param log error message, may be null * @return NetcdfDataset or null if failure * @throws IOException on read error */ public NetcdfDataset openDataset(InvDataset invDataset, boolean acquire, ucar.nc2.util.CancelTask task, Formatter log) throws IOException { Result result = new Result(); NetcdfDataset ncd = openDataset(invDataset, acquire, task, result); if (log != null) log.format("%s", result.errLog); return (result.fatalError) ? null : ncd; } private NetcdfDataset openDataset(InvDataset invDataset, boolean acquire, ucar.nc2.util.CancelTask task, Result result) throws IOException { IOException saveException = null; List accessList = new ArrayList(invDataset.getAccess()); // a list of all the accesses while (accessList.size() > 0) { InvAccess access = chooseDatasetAccess(accessList); // no valid access if (access == null) { result.errLog.format("No access that could be used in dataset %s %n", invDataset); if (saveException != null) throw saveException; return null; } String datasetLocation = access.getStandardUrlName(); ServiceType serviceType = access.getService().getServiceType(); if (debugOpen) System.out.println("ThreddsDataset.openDataset try " + datasetLocation + " " + serviceType); // deal with RESOLVER type if (serviceType == ServiceType.RESOLVER) { InvDatasetImpl rds = openResolver(datasetLocation, task, result); if (rds == null) return null; accessList = new ArrayList(rds.getAccess()); continue; } // ready to open it through netcdf API NetcdfDataset ds; // try to open try { ds = openDataset(access, acquire, task, result); } catch (IOException e) { result.errLog.format("Cant open %s %n err=%s%n", datasetLocation, e.getMessage()); if (debugOpen) { System.out.println("Cant open= " + datasetLocation + " " + serviceType); e.printStackTrace(); } accessList.remove(access); saveException = e; continue; } result.accessUsed = access; return ds; } // loop over accesses if (saveException != null) throw saveException; return null; } /** * Try to open invAccess as a NetcdfDataset. * * @param access open this InvAccess * @param acquire if true, aquire the dataset, else open it * @param task may be null * @param log error message, may be null * @return NetcdfDataset or null if failure * @throws IOException on read error */ public NetcdfDataset openDataset(InvAccess access, boolean acquire, ucar.nc2.util.CancelTask task, Formatter log) throws IOException { Result result = new Result(); NetcdfDataset ncd = openDataset(access, acquire, task, result); if (log != null) log.format("%s", result.errLog); return (result.fatalError) ? null : ncd; } private static boolean enhanceMode = false; private NetcdfDataset openDataset(InvAccess access, boolean acquire, ucar.nc2.util.CancelTask task, Result result) throws IOException { InvDataset invDataset = access.getDataset(); String datasetId = invDataset.getID(); String title = invDataset.getName(); String datasetLocation = access.getStandardUrlName(); ServiceType serviceType = access.getService().getServiceType(); if (debugOpen) System.out.println("ThreddsDataset.openDataset= " + datasetLocation); // deal with RESOLVER type if (serviceType == ServiceType.RESOLVER) { InvDatasetImpl rds = openResolver(datasetLocation, task, result); if (rds == null) return null; return openDataset(rds, acquire, task, result); } // ready to open it through netcdf API NetcdfDataset ds; // open DODS type String prefix = null; if ((serviceType == ServiceType.OPENDAP)|| (serviceType == ServiceType.DODS)) prefix = "dods:"; else if(serviceType == ServiceType.DAP4) prefix = "dap4:"; if(prefix != null) { String curl = datasetLocation; if (curl.startsWith("http:")) { curl = prefix + datasetLocation.substring(5); } else if (curl.startsWith("https:")) { curl = prefix + curl.substring(6); } ds = acquire ? NetcdfDataset.acquireDataset(curl, enhanceMode, task) : NetcdfDataset.openDataset(curl, enhanceMode, task); } // open CdmRemote else if (serviceType == ServiceType.CdmRemote) { String curl = CdmRemote.canonicalURL(datasetLocation); ds = acquire ? NetcdfDataset.acquireDataset(curl, enhanceMode, task) : NetcdfDataset.openDataset(curl, enhanceMode, task); } // open CdmRemote else if ((serviceType == ServiceType.HTTP) || (serviceType == ServiceType.HTTPServer)) { String curl = (datasetLocation.startsWith("http:")) ? "nodods:" + datasetLocation.substring(5) : datasetLocation; ds = acquire ? NetcdfDataset.acquireDataset(curl, enhanceMode, task) : NetcdfDataset.openDataset(curl, enhanceMode, task); } else { // open through NetcdfDataset API ds = acquire ? NetcdfDataset.acquireDataset(datasetLocation, enhanceMode, task) : NetcdfDataset.openDataset(datasetLocation, enhanceMode, task); } result.accessUsed = access; if (ds == null) return null; ds.setId(datasetId); ds.setTitle(title); annotate(invDataset, ds); // see if there's metadata LOOK whats this List list = invDataset.getMetadata(MetadataType.NcML); if (list.size() > 0) { InvMetadata ncmlMetadata = list.get(0); NcMLReader.wrapNcML(ds, ncmlMetadata.getXlinkHref(), null); } return ds; } /** * Find the "best" access in case theres more than one, based on what the CDM knows * how to open and use. * * @param accessList choose from this list. * @return best access method. */ public InvAccess chooseDatasetAccess(List accessList) { if (accessList.size() == 0) return null; // the order indicates preference InvAccess access = findAccessByServiceType(accessList, ServiceType.CdmRemote); if (access == null) access = findAccessByServiceType(accessList, ServiceType.DODS); if (access == null) access = findAccessByServiceType(accessList, ServiceType.OPENDAP); if (access == null) access = findAccessByServiceType(accessList, ServiceType.DAP4); if (access == null) access = findAccessByServiceType(accessList, ServiceType.FILE); // should mean that it can be opened through netcdf API if (access == null) access = findAccessByServiceType(accessList, ServiceType.NETCDF); // ServiceType.NETCDF is deprecated, use FILE // look for HTTP with format we can read if (access == null) { InvAccess tryAccess = findAccessByServiceType(accessList, ServiceType.HTTPServer); if (tryAccess == null) tryAccess = findAccessByServiceType(accessList, ServiceType.HTTP); // ServiceType.HTTP should be HTTPServer if (tryAccess != null) { DataFormatType format = tryAccess.getDataFormatType(); // these are the file types we can read if ((DataFormatType.BUFR == format) || (DataFormatType.GINI == format) || (DataFormatType.GRIB1 == format) || (DataFormatType.GRIB2 == format) || (DataFormatType.HDF5 == format) || (DataFormatType.NCML == format) || (DataFormatType.NETCDF == format) || (DataFormatType.NEXRAD2 == format) || (DataFormatType.NIDS == format)) { access = tryAccess; } } } // ADDE if (access == null) access = findAccessByServiceType(accessList, ServiceType.ADDE); // RESOLVER if (access == null) { access = findAccessByServiceType(accessList, ServiceType.RESOLVER); } return access; } private InvDatasetImpl openResolver(String urlString, ucar.nc2.util.CancelTask task, Result result) { InvCatalogFactory catFactory = new InvCatalogFactory("", false); InvCatalogImpl catalog = catFactory.readXML(urlString); if (catalog == null) { result.errLog.format("Couldnt open Resolver %s %n ", urlString); return null; } StringBuilder buff = new StringBuilder(); if (!catalog.check(buff)) { result.errLog.format("Invalid catalog from Resolver <%s>%n%s%n", urlString, buff.toString()); result.fatalError = true; return null; } InvDataset top = catalog.getDataset(); if (top.hasAccess()) return (InvDatasetImpl) top; else { java.util.List datasets = top.getDatasets(); return (InvDatasetImpl) datasets.get(0); } } /** * Add information from the InvDataset to the NetcdfDataset. * * @param ds get info from here * @param ncDataset add to here */ public static void annotate(InvDataset ds, NetcdfDataset ncDataset) { ncDataset.setTitle(ds.getName()); ncDataset.setId(ds.getID()); // add properties as global attributes for (InvProperty p : ds.getProperties()) { String name = p.getName(); if (null == ncDataset.findGlobalAttribute(name)) { ncDataset.addAttribute(null, new Attribute(name, p.getValue())); } } /* ThreddsMetadata.GeospatialCoverage geoCoverage = ds.getGeospatialCoverage(); if (geoCoverage != null) { if ( null != geoCoverage.getNorthSouthRange()) { ncDataset.addAttribute(null, new Attribute("geospatial_lat_min", new Double(geoCoverage.getLatSouth()))); ncDataset.addAttribute(null, new Attribute("geospatial_lat_max", new Double(geoCoverage.getLatNorth()))); } if ( null != geoCoverage.getEastWestRange()) { ncDataset.addAttribute(null, new Attribute("geospatial_lon_min", new Double(geoCoverage.getLonWest()))); ncDataset.addAttribute(null, new Attribute("geospatial_lon_max", new Double(geoCoverage.getLonEast()))); } if ( null != geoCoverage.getUpDownRange()) { ncDataset.addAttribute(null, new Attribute("geospatial_vertical_min", new Double(geoCoverage.getHeightStart()))); ncDataset.addAttribute(null, new Attribute("geospatial_vertical_max", new Double(geoCoverage.getHeightStart() + geoCoverage.getHeightExtent()))); } } DateRange timeCoverage = ds.getTimeCoverage(); if (timeCoverage != null) { ncDataset.addAttribute(null, new Attribute("time_coverage_start", timeCoverage.getStart().toDateTimeStringISO())); ncDataset.addAttribute(null, new Attribute("time_coverage_end", timeCoverage.getEnd().toDateTimeStringISO())); } */ ncDataset.finish(); } ////////////////////////////////////////////////////////////////////////////////// // image // look for an access method for an image datatype private InvAccess getImageAccess(InvDataset invDataset, ucar.nc2.util.CancelTask task, Result result) { List accessList = new ArrayList(invDataset.getAccess()); // a list of all the accesses while (accessList.size() > 0) { InvAccess access = chooseImageAccess(accessList); if (access != null) return access; // next choice is resolver type. access = invDataset.getAccess(ServiceType.RESOLVER); // no valid access if (access == null) { result.errLog.format("No access that could be used for Image Type %s %n", invDataset); return null; } // deal with RESOLVER type String datasetLocation = access.getStandardUrlName(); InvDatasetImpl rds = openResolver(datasetLocation, task, result); if (rds == null) return null; // use the access list from the resolved dataset accessList = new ArrayList(invDataset.getAccess()); } // loop over accesses return null; } private InvAccess chooseImageAccess(List accessList) { InvAccess access; access = findAccessByDataFormatType(accessList, DataFormatType.JPEG); if (access != null) return access; access = findAccessByDataFormatType(accessList, DataFormatType.GIF); if (access != null) return access; access = findAccessByDataFormatType(accessList, DataFormatType.TIFF); if (access != null) return access; access = findAccessByServiceType(accessList, ServiceType.ADDE); if (access != null) { String datasetLocation = access.getStandardUrlName(); if (datasetLocation.indexOf("image") > 0) return access; } return access; } /////////////////////////////////////////////////////////////////// // works against the accessList instead of the dataset list, so we can remove and try again private InvAccess findAccessByServiceType(List accessList, ServiceType type) { for (InvAccess a : accessList) { if (type.toString().equalsIgnoreCase(a.getService().getServiceType().toString())) return a; } return null; } // works against the accessList instead of the dataset list, so we can remove and try again private InvAccess findAccessByDataFormatType(List accessList, DataFormatType type) { for (InvAccess a : accessList) { if (type.toString().equalsIgnoreCase(a.getDataFormatType().toString())) return a; } return null; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy