Java tutorial
/* * ///////////////////////////////////////////////////////////////////////////// * // This file is part of the "Hyrax Data Server" project. * // * // * // Copyright (c) 2013 OPeNDAP, Inc. * // Author: Nathan David Potter <ndp@opendap.org> * // * // This library is free software; you can redistribute it and/or * // modify it under the terms of the GNU Lesser General Public * // License as published by the Free Software Foundation; either * // version 2.1 of the License, or (at your option) any later version. * // * // This library is distributed in the hope that it will be useful, * // but WITHOUT ANY WARRANTY; without even the implied warranty of * // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * // Lesser General Public License for more details. * // * // You should have received a copy of the GNU Lesser General Public * // License along with this library; if not, write to the Free Software * // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * // * // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112. * ///////////////////////////////////////////////////////////////////////////// */ package opendap.threddsHandler; import opendap.namespaces.THREDDS; import opendap.namespaces.XLINK; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.Options; import org.apache.commons.cli.PosixParser; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.filter.ElementFilter; import org.jdom.input.SAXBuilder; import org.jdom.output.Format; import org.jdom.output.XMLOutputter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.PrintStream; import java.net.MalformedURLException; import java.net.URL; import java.util.*; /** * This class is a thredds catalog utility. */ public class ThreddsCatalogUtil { private XMLOutputter xmlo = null; private Logger log = LoggerFactory.getLogger(ThreddsCatalogUtil.class); /** * Constructor. * */ public ThreddsCatalogUtil() { xmlo = new XMLOutputter(Format.getPrettyFormat()); } /** * Implements a modified depth-first traversal of a thredds catalog. The * catalog is treated as a tree-like structure, but since it is really a * directed graph, catalog URLs are cached and the same URL is neither * returned or 'crawled' twice. The traversal is like a depth-first * traversal, but instead of recurring all the way to the leaf nodes of the * pseudo-tree, it descends as it returns nodes. The seed URL is used to * initialize the stack of child nodes and then nextElement() both returns * the top node and visits that node, pushing all of its URLs onto the * stack. This limits the HTTP accesses to at most one-per nextElement call. * * @note If constructed to use a cache (the default), catalog URLs will be * cached along with the catalog responses. * * @author jimg * */ public class threddsCrawlerEnumeration implements Enumeration<String> { // This holds catalogs not yet seen by the user of the Enumeration private Stack<String> childURLs; threddsCrawlerEnumeration(String catalogURL) { childURLs = new Stack<String>(); childURLs.push(catalogURL); } private void recur(String catalogURL) { try { Vector<String> URLs = getCatalogRefURLs(catalogURL, false); if (URLs != null) { for (String URL : URLs) { childURLs.push(URL); } } } catch (InterruptedException e) { log.error("recur(): Caught InterruptedException returning with recursion incomplete!"); } } @Override public boolean hasMoreElements() { return !childURLs.isEmpty(); } @Override public String nextElement() { try { String child = childURLs.pop(); recur(child); return child; } catch (EmptyStackException e) { return null; } } } public static enum SERVICE { ALL, ADDE { public String toString() { return "ADDE"; } }, DODS { public String toString() { return "DODS"; } }, OPeNDAP { public String toString() { return "OPeNDAP"; } }, OPeNDAP_G { public String toString() { return "OPeNDAP-G"; } }, HTTPServer { public String toString() { return "HTTPServer"; } }, GridFTP { public String toString() { return "GridFTP"; } }, File { public String toString() { return "File"; } }, LAS { public String toString() { return "LAS"; } }, WMS { public String toString() { return "WMS"; } }, WFS { public String toString() { return "WFS"; } }, WCS { public String toString() { return "WCS"; } }, WSDL { public String toString() { return "WSDL"; } }, WebForm { public String toString() { return "WebForm"; } }, Catalog { public String toString() { return "Catalog"; } }, QueryCapability { public String toString() { return "QueryCapability"; } }, Resolver { public String toString() { return "Resolver"; } }, Compound { public String toString() { return "Compound"; } } } public static void main(String[] args) throws Exception { try { Options options = createCmdLineOptions(); CommandLineParser parser = new PosixParser(); CommandLine cmd = parser.parse(options, args); ThreddsCatalogUtil tcc = new ThreddsCatalogUtil(); // tcc.testGetCatalogURLs("http://test.opendap.org:8090/opendap/data/catalog.xml"); // tcc.getDataAccessURLs("http://crawlTest.opendap.org:8080/opendap/coverage/catalog.xml",datasetURLs); // tcc.getDataAccessURLs("http://motherlode.ucar.edu:8080/thredds/idd/satellite.xml",datasetURLs); // http://motherlode.ucar.edu:8080/thredds/catalog/satellite/3.9/PR-REGIONAL_4km/20091203/catalog.html // tcc.getDataAccessURLs("file:/Users/ndp/hyrax/ioos/crawlTest.xml",datasetURLs); // tcc.getDataAccessURLs("file:/Users/ndp/hyrax/ioos/satellite.xml",datasetURLs); // tcc.getDataAccessURLs("http://motherlode.ucar.edu:8080/thredds/catalog/satellite/3.9/PR-REGIONAL_4km/20091203/catalog.xml",datasetURLs); // tcc.getDataAccessURLs("http://motherlode.ucar.edu:8080/thredds/idd/satellite.xml",datasetURLs); // tcc.getDataAccessURLs("http://crawlTest.opendap.org:8080/opendap/catalog.xml",datasetURLs); // tcc.getDataAccessURLs("http://crawlTest.opendap.org:8080/opendap/data/catalog.xml",datasetURLs); // tcc.getDataAccessURLs("http://oceanwatch.pfeg.noaa.gov/thredds/catalog.xml",datasetURLs); Vector<String> urls = tcc.getDDXUrls("http://localhost:8080/opendap/catalog.xml", true); for (String url : urls) { System.out.println(url); } // tcc.crawlTest(System.out,"http://localhost:8080/opendap/catalog.xml",cmd.hasOption("r")); // tcc.crawlTest(System.out, "http://blackburn.whoi.edu:8081/thredds/bathy_catalog.xml", cmd.hasOption("r")); // tcc.crawlTest(System.out,"http://motherlode.ucar.edu:8080/thredds/idd/satellite.xml",false); } finally { ; } } private static Options createCmdLineOptions() { Options options = new Options(); options.addOption("e", false, "encode the command line arguments, cannot be used in conjunction with -d (decode)."); options.addOption("d", false, "decode the command line arguments, cannot be used in conjunction with -e (encode)."); options.addOption("t", false, "runs internal tests and produces output on stdout."); options.addOption("r", false, "recursively descend nested THREDDS catalogs."); return options; } public void crawlTest(PrintStream ps, String catalogURLString, boolean recurse) throws InterruptedException { Vector<String> datasetURLs; Vector<String> catalogURLs; ps.println("#########################################################"); ps.println("Testing THREDDS Catalog Crawl Functions."); ps.println(""); ps.println("Using THREDDS catalog URL: " + catalogURLString); ps.println("Recursion is " + (recurse ? "ON" : "OFF")); ps.println(""); ps.println("- - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); ps.println("Retrieving DAP data set access URLs"); ps.println(""); datasetURLs = getDataAccessURLs(catalogURLString, SERVICE.OPeNDAP, recurse); for (String datasetURL : datasetURLs) { ps.println(" Found DAP data set URL: " + datasetURL); } ps.println("Located " + datasetURLs.size() + " access URLs."); ps.println(""); ps.println("- - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); ps.println("Dumping all DDX documents found in catalog"); ps.println(""); printDDXDocuments(ps, catalogURLString, recurse); ps.println(""); ps.println("- - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); ps.println("Retrieving THREDDS catalog URLs"); ps.println(""); catalogURLs = getCatalogRefURLs(catalogURLString, recurse); for (String catalogURL : catalogURLs) { ps.println(" Found THREDDS Catalog URL: " + catalogURL); } ps.println("Located " + catalogURLs.size() + " THREDDS Catalog URLs."); } public void printDDXDocuments(PrintStream ps, String catalogUrlString, boolean recurse) throws InterruptedException { Vector<Document> ddxDocs = getDDXDocuments(catalogUrlString, recurse); for (Document doc : ddxDocs) { try { xmlo.output(doc, ps); } catch (IOException e) { log.error(e.getMessage()); } ps.println("\n"); } } public Vector<Element> getDDXRootElements(String catalogUrlString, boolean recurse) throws InterruptedException { Vector<Element> ddxRootElements = new Vector<Element>(); Vector<String> ddxUrls = getDDXUrls(catalogUrlString, recurse); for (String url : ddxUrls) { ddxRootElements.add(getDocumentRoot(url)); } return ddxRootElements; } public Vector<Document> getDDXDocuments(String catalogUrlString, boolean recurse) throws InterruptedException { Vector<Document> ddxDocs = new Vector<Document>(); Vector<String> ddxUrls = getDDXUrls(catalogUrlString, recurse); for (String url : ddxUrls) { ddxDocs.add(getDocument(url)); } return ddxDocs; } public Vector<String> getDDXUrls(String catalogUrlString, boolean recurse) throws InterruptedException { Vector<String> datasetUrls = getDataAccessURLs(catalogUrlString, SERVICE.OPeNDAP, recurse); String url; for (int i = 0; i < datasetUrls.size(); i++) { url = datasetUrls.get(i); log.debug("Found DAP dataset URL: " + url); datasetUrls.set(i, url + ".ddx"); } return datasetUrls; } /** * Returns all of the THREDDS catalog URLs in the passed catalog element. * * @param catalogUrlString * The URL from where the catalog was retrieved. * @param catalog * The root element (the catalog element) in a THREDDS catalog * document. * @param recurse * If true the code will recursively descend into all of the * child catalogs and return all the contained catalog URLs. Be * Careful! * @return A vector of fully qualified URL Strings each of which points to a * THREDDS catalog document. */ private Vector<String> getCatalogRefURLs(String catalogUrlString, Element catalog, boolean recurse) throws InterruptedException { Vector<String> catalogURLs = new Vector<String>(); try { String href; String newCatalogURL; Element catalogRef; Iterator i = catalog.getDescendants(new ElementFilter("catalogRef", THREDDS.NS)); while (i.hasNext()) { catalogRef = (Element) i.next(); href = catalogRef.getAttributeValue("href", XLINK.NS); newCatalogURL = getCatalogURL(catalogUrlString, href); catalogURLs.add(newCatalogURL); if (recurse) catalogURLs.addAll(getCatalogRefURLs(newCatalogURL, recurse)); } } catch (MalformedURLException e) { log.error("Malformed URL Exception: " + catalogUrlString + " msg: " + e.getMessage()); } return catalogURLs; } /** * Returns all of the THREDDS catalog URLs contained in the THREDDS catalog * located at the passed URL. * * @param catalogUrlString * The URL from where the catalog was retrieved. * @param recurse * If true the code will recursively descend into all of the * child catalogs and return all the contained catalog URLs. Be * Careful! * @return A vector of fully qualified URL Strings each of which points to a * THREDDS catalog document. If the catalog returned by * dereferencing <code>catalogUrlString</code> is 'bad' (e.g., the * server returns a 404 response), then the Vector<Sting> result * will be empty. */ public Vector<String> getCatalogRefURLs(String catalogUrlString, boolean recurse) throws InterruptedException { Vector<String> catalogURLs = new Vector<String>(); Element catalog = getDocumentRoot(catalogUrlString); if (catalog != null) catalogURLs = getCatalogRefURLs(catalogUrlString, catalog, recurse); return catalogURLs; } /** * Crawl a thredds catalog. This implements a modified depth-first traversal * of the 'tree' of catalogs with 'topCatalog' as the root. In reality, a * thredds catalog is a directed graph but the Enumeration returned is smart * enough to avoid loops, so the resulting traversal has a tree-like feel. * The algorithm is like a depth-first traversal but it has been modified so * that HTTP accesses are limited to one per call to nextElement(). When * nextElement is called, its value (call it 'C') is both returned and * crawled so that subsequent calls return the children of 'C'. A real * depth-first traversal would descend all the way to the leaf nodes - * thredds catalogs that contain only references to data set and not other * catalogs. In addition to cutting down on HTTP-induced latency (by capping * the number of calls per invocation of nextElement()), this also ensures * that the client of the Enumeration will see all of the catalogs, * including 'interior' ones. * * @note By default, this uses the THREDDS Catalog cache. If you want to * crawl catalogs using an Enumeration and not cache the result, use the * other version of this method and pass false for the useCache parameter. * * @param topCatalog * The THREDDS catalog that will serve as the root node * @return An Enumeration of Strings that will visit all of the catalogs in * that tree * @throws Exception Thrown if the cache cannot be configured */ public Enumeration<String> getCatalogEnumeration(String topCatalog) throws InterruptedException { return new threddsCrawlerEnumeration(topCatalog); } /** * Crawl a collection of thredds catalogs using a Enumeration. This does not * perform a complete crawl and then return the Enumeration but, instead, * the HTTP calls are interwoven with the Enumeration.nextElement() calls. * * @param topCatalog * The THREDDS Catalog to serve as the root node * @return An Enumeration * @throws Exception * Thrown if the cache cannot be configured */ public Enumeration<String> getCatalogURLs(String topCatalog) throws InterruptedException { return new threddsCrawlerEnumeration(topCatalog); } public static String getUrlInfo(URL url) throws InterruptedException { String info = "URL:\n"; info += " getHost(): " + url.getHost() + "\n"; info += " getAuthority(): " + url.getAuthority() + "\n"; info += " getFile(): " + url.getFile() + "\n"; info += " getSystemPath(): " + url.getPath() + "\n"; info += " getDefaultPort(): " + url.getDefaultPort() + "\n"; info += " getPort(): " + url.getPort() + "\n"; info += " getProtocol(): " + url.getProtocol() + "\n"; info += " getQuery(): " + url.getQuery() + "\n"; info += " getRef(): " + url.getRef() + "\n"; info += " getUserInfo(): " + url.getUserInfo() + "\n"; return info; } private String getServerUrlString(URL url) throws InterruptedException { String baseURL = null; String protocol = url.getProtocol(); if (protocol.equalsIgnoreCase("file")) { log.debug("Protocol is FILE."); } else if (protocol.equalsIgnoreCase("http")) { log.debug("Protcol is HTTP."); String host = url.getHost(); /* String path = url.getPath(); */ int port = url.getPort(); baseURL = protocol + "://" + host; if (port != -1) baseURL += ":" + port; } log.debug("ServerURL: " + baseURL); return baseURL; } private String getServerUrlString(String url) throws InterruptedException, MalformedURLException { URL u = new URL(url); return getServerUrlString(u); } private Vector<String> getDataAccessURLs(String catalogUrlString, Element catalog, SERVICE service, boolean recurse) throws InterruptedException { Vector<String> serviceURLs = new Vector<String>(); try { URL catalogURL = new URL(catalogUrlString); String serverURL = getServerUrlString(catalogURL); String msg; HashMap<String, Element> services = collectServices(catalog, service); msg = "#### collectServices Found services:\n"; for (String srvcName : services.keySet()) msg += "#### Service Name: " + srvcName + "\n" + xmlo.outputString(services.get(srvcName)) + "\n"; log.debug(msg); Element dataset; Iterator i = catalog.getChildren(THREDDS.DATASET, THREDDS.NS).iterator(); while (i.hasNext()) { dataset = (Element) i.next(); collectDatasetAccessUrls(dataset, services, null, serverURL, serviceURLs); } log.debug("#### Accumulated " + serviceURLs.size() + " access URLs."); if (serverURL != null && recurse) { String href; Element catalogRef; String newCatalogURL; i = catalog.getDescendants(new ElementFilter("catalogRef", THREDDS.NS)); while (i.hasNext()) { catalogRef = (Element) i.next(); href = catalogRef.getAttributeValue("href", XLINK.NS); newCatalogURL = getCatalogURL(catalogUrlString, href); serviceURLs.addAll(getDataAccessURLs(newCatalogURL, service, recurse)); } } } catch (MalformedURLException e) { log.error("Unable to load THREDDS catalog: " + catalogUrlString + " msg: " + e.getMessage()); } return serviceURLs; } /** * Returns a vector of data access URIs from The THREDDS catalog located at * the URL contained in the passed parameter String * <code>catalogUrlString</code>. * * @param catalogUrlString * The THREDDS catalog to crawl. * @param catalogDoc * @param service * The SERVICE whose data access URLs you wish to get. * @param recurse * Controls recursion. A value of True will cause the software to * recursively traverse the catalog (via thredds:catalogRef * elements) in search of data access URLs. * @return The vector of data access URLs. */ /* private Vector<String> getDataAccessURLs(String catalogUrlString, Document catalogDoc, SERVICE service, boolean recurse) { Vector<String> serviceURLs; Element catalog = catalogDoc.getRootElement(); serviceURLs = getDataAccessURLs(catalogUrlString, catalog, service, recurse); return serviceURLs; // log.warn("Thredds Catalog ingest not yet supported."); } */ /** * Returns a vector of data access URIs from The THREDDS catalog located at * the URL contained in the passed parameter String * <code>catalogUrlString</code>. * * @param catalogUrlString * The THREDDS catalog to crawl. * @param service * The SERVICE whose data access URLs you wish to get. * @param recurse * Controls recursion. A value of True will cause the software to * recursively traverse the catalog (via thredds:catalogRef * elements) in search of data access URLs. * @return The vector of data access URLs. If the catalog returned by * dereferencing <code>catalogUrlString</code> is 'bad' (e.g., the * server returns a 404 response), then the Vector<Sting> result * will be empty. */ public Vector<String> getDataAccessURLs(String catalogUrlString, SERVICE service, boolean recurse) throws InterruptedException { Vector<String> serviceURLs = new Vector<String>(); Element catalog = getDocumentRoot(catalogUrlString); if (catalog != null) serviceURLs = getDataAccessURLs(catalogUrlString, catalog, service, recurse); return serviceURLs; } /** * Returns the root Element of the XML document located at the URL contained * in the passed parameter <code>docUrlString</code> * * @param docUrlString * The URL of the document to retrieve * @return The Document */ private Element getDocumentRoot(String docUrlString) throws InterruptedException { Element docRoot = null; Document doc = getDocument(docUrlString); if (doc != null) { docRoot = doc.getRootElement(); } return docRoot; } /** * Returns the Document object for the XML document located at the URL * contained in the passed parameter String <code>docUrlString</code>. * * @note This is the point in the class where a response is (possibly) * cached. * * @param docUrlString * The URL of the document to retrieve. * @return The Document */ private Document getDocument(String docUrlString) throws InterruptedException { Document doc = null; try { URL docUrl = new URL(docUrlString); SAXBuilder sb = new SAXBuilder(); log.debug("Retrieving XML Document: " + docUrlString); log.debug("Document URL INFO: \n" + getUrlInfo(docUrl)); doc = sb.build(docUrl); log.debug("Loaded XML Document: \n" + xmlo.outputString(doc)); } catch (MalformedURLException e) { log.error("Problem with XML Document URL: " + docUrlString + " Caught a MalformedURLException. Message: " + e.getMessage()); } catch (IOException e) { log.error("Problem retrieving XML Document: " + docUrlString + " Caught a IOException. Message: " + e.getMessage()); } catch (JDOMException e) { log.error("Problem parsing XML Document: " + docUrlString + " Caught a JDOMException. Message: " + e.getMessage()); } return doc; } private String getCatalogURL(String catalogUrlString, String href) throws InterruptedException, MalformedURLException { if (href.startsWith("/")) { href = getServerUrlString(catalogUrlString) + href; } else if (!href.startsWith("http://")) { log.debug("catalogUrlString: " + catalogUrlString); log.debug("href: " + href); String s; s = catalogUrlString.substring(0, catalogUrlString.lastIndexOf("/")); if (!s.endsWith("/")) s += "/"; log.debug("s: " + s); href = s + href; } log.debug("Built THREDDS catalog URL:'" + href + "'"); return href; } private HashMap<String, Element> collectServices(Element threddsCatalog) throws InterruptedException { HashMap<String, Element> services = new HashMap<String, Element>(); Iterator i = threddsCatalog.getDescendants(new ElementFilter(THREDDS.SERVICE, THREDDS.NS)); Element srvcElem; while (i.hasNext()) { srvcElem = (Element) i.next(); services.put(srvcElem.getAttributeValue("name"), srvcElem); } return services; } private HashMap<String, Element> collectServices(Element threddsCatalog, SERVICE s) throws InterruptedException { HashMap<String, Element> services = collectServices(threddsCatalog); HashMap<String, Element> childSrvcs; // If they aren't asking for everything... if (s != SERVICE.ALL) { /* boolean done = false; */ Element service; Vector<String> taggedForRemoval = new Vector<String>(); for (String serviceName : services.keySet()) { service = services.get(serviceName); if (service.getAttributeValue("serviceType").equalsIgnoreCase(SERVICE.Compound.toString())) { childSrvcs = collectServices(service, s); if (childSrvcs.isEmpty()) { taggedForRemoval.add(serviceName); } } else if (!service.getAttributeValue("serviceType").equalsIgnoreCase(s.toString())) { taggedForRemoval.add(serviceName); } } for (String serviceName : taggedForRemoval) { services.remove(serviceName); } } return services; } private void collectDatasetAccessUrls(Element dataset, HashMap<String, Element> services, String inheritedServiceName, String baseServerURL, Vector<String> datasetURLs) throws InterruptedException { String urlPath; String serviceName; String s; Element metadata, dset, access; String datasetName; /* Iterator i; */ log.debug("inheritedServiceName: " + inheritedServiceName); serviceName = dataset.getAttributeValue("serviceName"); urlPath = dataset.getAttributeValue("urlPath"); metadata = dataset.getChild("metadata", THREDDS.NS); datasetName = dataset.getAttributeValue("name"); if (metadata != null && metadata.getAttributeValue("inherited").equalsIgnoreCase("true")) { log.debug("Found inherited metadata"); s = metadata.getChildText("serviceName", THREDDS.NS); if (s != null) { inheritedServiceName = s; log.debug("Updated inheritedServiceName to: " + inheritedServiceName); } } if (urlPath != null) { log.debug("<dataset> has urlPath attribute: " + urlPath); if (serviceName == null) { log.debug("<dataset> missing serviceName attribute. Checking for child element..."); serviceName = dataset.getChildText("serviceName", THREDDS.NS); } if (serviceName == null) { log.debug("<dataset> missing serviceName childElement. Checking for inherited serviceName..."); serviceName = inheritedServiceName; } if (serviceName != null) { log.debug("<dataset> has serviceName: " + serviceName); datasetURLs.addAll(getAccessURLs(urlPath, serviceName, services, baseServerURL)); } } Iterator i = dataset.getChildren("access", THREDDS.NS).iterator(); while (i.hasNext()) { access = (Element) i.next(); log.debug("Located thredds:access element in dataset '" + datasetName + "'"); datasetURLs.addAll(getAccessURLs(access, services, baseServerURL)); } i = dataset.getChildren(THREDDS.DATASET, THREDDS.NS).iterator(); while (i.hasNext()) { dset = (Element) i.next(); collectDatasetAccessUrls(dset, services, inheritedServiceName, baseServerURL, datasetURLs); } } private Vector<String> getAccessURLs(Element access, HashMap<String, Element> services, String baseServerURL) throws InterruptedException { String serviceName = access.getAttributeValue("serviceName"); String urlPath = access.getAttributeValue("urlPath"); return getAccessURLs(urlPath, serviceName, services, baseServerURL); } private Vector<String> getAccessURLs(String urlPath, String serviceName, HashMap<String, Element> services, String baseServerURL) throws InterruptedException { Vector<String> accessURLs = new Vector<String>(); String access, base, serviceType, sname; /* Iterator i; */ Element srvc; Element service = services.get(serviceName); if (service != null) { serviceType = service.getAttributeValue("serviceType"); if (serviceType.equalsIgnoreCase("Compound")) { Iterator i = service.getChildren("service", THREDDS.NS).iterator(); while (i.hasNext()) { srvc = (Element) i.next(); sname = srvc.getAttributeValue("name"); Vector<String> v = getAccessURLs(urlPath, sname, services, baseServerURL); accessURLs.addAll(v); } } else { base = service.getAttributeValue("base"); access = baseServerURL + base + urlPath; accessURLs.add(access); log.debug("#### Found access URL: " + access); } } return accessURLs; } }