/* * Copyright 1998-2015 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 thredds.core; import org.springframework.beans.factory.annotation.Autowired; import thredds.client.catalog.*; import thredds.client.catalog.writer.DatasetHtmlWriter; import thredds.server.config.HtmlConfig; import thredds.servlet.HtmlWriter; import thredds.util.ContentType; import ucar.nc2.units.DateType; import ucar.unidata.util.Format; import ucar.unidata.util.StringUtil2; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.net.URI; import java.net.URISyntaxException; import java.util.Formatter; import java.util.List; import static thredds.servlet.ServletUtil.setResponseContentLength; /** * Write HTML representations of a Catalog or Dataset * * @author caron * @since 1/19/2015 */ public class ConfigCatalogHtmlWriter { static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConfigCatalogHtmlWriter.class); @Autowired private ViewerService viewerService; HtmlWriter html; HtmlConfig htmlConfig; String contextPath; public ConfigCatalogHtmlWriter(HtmlWriter html, HtmlConfig htmlConfig, String contextPath) { this.html = html; this.htmlConfig = htmlConfig; this.contextPath = contextPath; } /** * Write an Catalog to the HttpServletResponse, return the size in bytes of the catalog written to the response. * * @param req the HttpServletRequest * @param res the HttpServletResponse. * @param cat the InvCatalogImpl to write to the HttpServletResponse. * @param isLocalCatalog indicates whether this catalog is local to this server. * @return the size in bytes of the catalog written to the HttpServletResponse. * @throws IOException if problems writing the response. */ public int writeCatalog(HttpServletRequest req, HttpServletResponse res, Catalog cat, boolean isLocalCatalog) throws IOException { String catHtmlAsString = convertCatalogToHtml(cat, isLocalCatalog); // Once this header is set, we know the encoding, and thus the actual // number of *bytes*, not characters, to encode res.setContentType(ContentType.html.getContentHeader()); int len = setResponseContentLength(res, catHtmlAsString); if (!req.getMethod().equals("HEAD")) { PrintWriter writer = res.getWriter(); writer.write(catHtmlAsString); writer.flush(); } return len; } /** * Write a catalog in HTML, make it look like a file directory. * * @param cat catalog to write */ String convertCatalogToHtml(Catalog cat, boolean isLocalCatalog) { StringBuilder sb = new StringBuilder(10000); String uri = cat.getUriString(); if (uri == null) uri = cat.getName(); if (uri == null) uri = "unknown"; String catname = StringUtil2.quoteHtmlContent(uri); // Render the page header sb.append(html.getHtmlDoctypeAndOpenTag()); // "<html>\n" ); sb.append("<head>\r\n"); sb.append("<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>"); sb.append("<title>"); //if (cat.isStatic()) // sb.append("TdsStaticCatalog ").append(catname); // for searching //else sb.append("Catalog ").append(catname); sb.append("</title>\r\n"); sb.append(html.getTdsCatalogCssLink()).append("\n"); sb.append("</head>\r\n"); sb.append("<body>"); sb.append("<h1>"); // Logo //String logoUrl = this.htmlConfig.getInstallLogoUrl(); String logoUrl = htmlConfig.prepareUrlStringForHtml(htmlConfig.getInstallLogoUrl()); if (logoUrl != null) { sb.append("<img src='").append(logoUrl); String logoAlt = htmlConfig.getInstallLogoAlt(); if (logoAlt != null) sb.append("' alt='").append(logoAlt); sb.append("' align='left' valign='top'").append(">\n"); } sb.append("Catalog ").append(catname); sb.append("</h1>"); sb.append("<HR size='1' noshade='noshade'>"); sb.append("<table width='100%' cellspacing='0' cellpadding='5' align='center'>\r\n"); // Render the column headings sb.append("<tr>\r\n"); sb.append("<th align='left'><font size='+1'>"); sb.append("Dataset"); sb.append("</font></th>\r\n"); sb.append("<th align='center'><font size='+1'>"); sb.append("Size"); sb.append("</font></th>\r\n"); sb.append("<th align='right'><font size='+1'>"); sb.append("Last Modified"); sb.append("</font></th>\r\n"); sb.append("</tr>"); // Recursively render the datasets doDatasets(cat, cat.getDatasets(), sb, false, 0, isLocalCatalog); // Render the page footer sb.append("</table>\r\n"); sb.append("<HR size='1' noshade='noshade'>"); html.appendSimpleFooter(sb); sb.append("</body>\r\n"); sb.append("</html>\r\n"); return (sb.toString()); } private boolean doDatasets(Catalog cat, List<Dataset> datasets, StringBuilder sb, boolean shade, int level, boolean isLocalCatalog) { //URI catURI = cat.getBaseURI(); String catHtml; if (!isLocalCatalog) { // Setup HREF url to link to HTML dataset page (more below). catHtml = contextPath + "/remoteCatalogService?command=subset&catalog=" + cat.getUriString() + "&"; // Can't be "/catalogServices?..." because subset decides on xml or html by trailing ".html" on URL path } else { // replace xml with html URI catURI = cat.getBaseURI(); // Get the catalog name - we want a relative URL catHtml = catURI.getPath(); if (catHtml == null) catHtml = cat.getUriString(); // if URI is a file int pos = catHtml.lastIndexOf("/"); if (pos != -1) catHtml = catHtml.substring(pos + 1); // change the ending to "catalog.html?" pos = catHtml.lastIndexOf('.'); if (pos < 0) catHtml = catHtml + "catalog.html?"; else catHtml = catHtml.substring(0, pos) + ".html?"; } for (Dataset ds : datasets) { String name = StringUtil2.quoteHtmlContent(ds.getName()); sb.append("<tr"); if (shade) sb.append(" bgcolor='#eeeeee'"); sb.append(">\r\n"); shade = !shade; sb.append("<td align='left'>"); for (int j = 0; j <= level; j++) sb.append("    "); sb.append("\r\n"); if (ds instanceof CatalogRef) { CatalogRef catref = (CatalogRef) ds; String href = catref.getXlinkHref(); if (!isLocalCatalog) { URI hrefUri = cat.getBaseURI().resolve(href); href = hrefUri.toString(); } try { URI uri = new URI(href); if (uri.isAbsolute()) { boolean defaultUseRemoteCatalogService = htmlConfig.getUseRemoteCatalogService(); // read default as set in threddsConfig.xml Boolean dsUseRemoteCatalogSerivce = ((CatalogRef) ds).useRemoteCatalogService(); // check to see if catalogRef contains tag that overrides default boolean useRemoteCatalogService = defaultUseRemoteCatalogService; // by default, use the option found in threddsConfig.xml if (dsUseRemoteCatalogSerivce == null) dsUseRemoteCatalogSerivce = defaultUseRemoteCatalogService; // if the dataset does not have the useRemoteDataset option set, opt for the default behavior // if the default is not the same as what is defined in the catalog, go with the catalog option // as the user has explicitly overridden the default if (defaultUseRemoteCatalogService != dsUseRemoteCatalogSerivce) { useRemoteCatalogService = dsUseRemoteCatalogSerivce; } // now, do the right thing with using the remoteCatalogService, or not if (useRemoteCatalogService) { href = contextPath + "/remoteCatalogService?catalog=" + href; } else { int pos = href.lastIndexOf('.'); href = href.substring(0, pos) + ".html"; } } else { int pos = href.lastIndexOf('.'); href = href.substring(0, pos) + ".html"; } } catch (URISyntaxException e) { log.error(href, e); } sb.append("<img src='").append(htmlConfig.prepareUrlStringForHtml(htmlConfig.getFolderIconUrl())) .append("' alt='").append(htmlConfig.getFolderIconAlt()).append("'>  "); sb.append("<a href='"); sb.append(StringUtil2.quoteHtmlContent(href)); sb.append("'><tt>"); sb.append(name); sb.append("/</tt></a></td>\r\n"); } else { // Not a CatalogRef if (ds.hasNestedDatasets()) sb.append("<img src='").append(htmlConfig.prepareUrlStringForHtml(htmlConfig.getFolderIconUrl())) .append("' alt='").append(htmlConfig.getFolderIconAlt()).append("'>  "); // Check if dataset has single resolver service. if (ds.getAccess().size() == 1 && ServiceType.Resolver == ds.getAccess().get(0).getService().getType()) { Access access = ds.getAccess().get(0); String accessUrlName = access.getUnresolvedUrlName(); int pos = accessUrlName.lastIndexOf(".xml"); if (accessUrlName.equalsIgnoreCase("latest.xml") && !isLocalCatalog) { String catBaseUriPath = ""; String catBaseUri = cat.getBaseURI().toString(); pos = catBaseUri.lastIndexOf("catalog.xml"); if (pos != -1) { catBaseUriPath = catBaseUri.substring(0, pos); } accessUrlName = contextPath + "/remoteCatalogService?catalog=" + catBaseUriPath + accessUrlName; } else if (pos != -1) { accessUrlName = accessUrlName.substring(0, pos) + ".html"; } sb.append("<a href='"); sb.append(StringUtil2.quoteHtmlContent(accessUrlName)); sb.append("'><tt>"); String tmpName = name; if (tmpName.endsWith(".xml")) { tmpName = tmpName.substring(0, tmpName.lastIndexOf('.')); } sb.append(tmpName); sb.append("</tt></a></td>\r\n"); } // Dataset with an ID. else if (ds.getID() != null) { // Write link to HTML dataset page. sb.append("<a href='"); sb.append(StringUtil2.quoteHtmlContent(catHtml)); sb.append("dataset="); sb.append(StringUtil2.replace(ds.getID(), '+', "%2B")); sb.append("'><tt>"); sb.append(name); sb.append("</tt></a></td>\r\n"); } // Dataset without an ID. else { sb.append("<tt>"); sb.append(name); sb.append("</tt></td>\r\n"); } } sb.append("<td align='right'><tt>"); double size = ds.getDataSize(); if ((size != 0.0) && !Double.isNaN(size)) { sb.append(Format.formatByteSize(size)); } else { sb.append(" "); } sb.append("</tt></td>\r\n"); sb.append("<td align='right'><tt>"); // Get last modified time. DateType lastModDateType = ds.getLastModifiedDate(); if (lastModDateType == null) { sb.append("--");// "Unknown"); } else { sb.append(lastModDateType.toDateTimeString()); } sb.append("</tt></td>\r\n"); sb.append("</tr>\r\n"); if (!(ds instanceof CatalogRef)) { shade = doDatasets(cat, ds.getDatasets(), sb, shade, level + 1, isLocalCatalog); } } return shade; } private String convertDatasetToHtml(String catURL, Dataset dataset, HttpServletRequest request, boolean isLocalCatalog) { Formatter out = new Formatter(); out.format("%s<head>\r\n", html.getHtmlDoctypeAndOpenTag()); out.format("<title> Catalog Services</title>\r\n"); out.format("<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>\r\n"); out.format("%s\r\n", html.getTdsPageCssLink()); out.format(html.getGoogleTrackingContent()); // String already has EOL. out.format("</head>\r\n"); out.format("<body>\r\n"); StringBuilder sb = new StringBuilder(); html.appendOldStyleHeader(sb); out.format("%s\r\n", sb); out.format("<h2> Catalog %s</h2>\r\n", catURL); DatasetHtmlWriter writer = new DatasetHtmlWriter(); // (Formatter out, Dataset ds, boolean complete, boolean isServer, boolean datasetEvents, boolean catrefEvents, boolean resolveRelativeUrls) writer.writeHtmlDescription(out, dataset, false, true, false, false, !isLocalCatalog); // optional access through Viewers if (isLocalCatalog) viewerService.showViewers(out, dataset, request); out.format("</body>\r\n"); out.format("</html>\r\n"); return out.toString(); } public int showDataset(String catURL, Dataset dataset, HttpServletRequest request, HttpServletResponse response, boolean isLocalCatalog) throws IOException { String datasetAsHtml = this.convertDatasetToHtml(catURL, dataset, request, isLocalCatalog); response.setStatus(HttpServletResponse.SC_OK); response.setContentType(ContentType.html.getContentHeader()); if (!request.getMethod().equals("HEAD")) { PrintWriter pw = response.getWriter(); pw.write(datasetAsHtml); pw.flush(); } return datasetAsHtml.length(); } }