/*
* Copyright 1998-2014 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.server.admin;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.*;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.coverity.security.Escape;
import org.quartz.JobKey;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import thredds.catalog.InvDatasetFeatureCollection;
import thredds.featurecollection.FeatureCollectionConfig;
import thredds.featurecollection.FeatureCollectionType;
import thredds.inventory.*;
import thredds.monitor.FmrcCacheMonitorImpl;
import thredds.server.config.TdsContext;
import thredds.servlet.DataRootHandler;
import thredds.util.ContentType;
import thredds.util.TdsPathUtils;
import ucar.nc2.constants.CDM;
import ucar.unidata.util.StringUtil2;
/**
* Allow external triggers for rereading Feature collections
*
* @author caron
* @since May 4, 2010
*/
@Controller
@RequestMapping(value={"/admin"})
public class CollectionController {
private final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(getClass());
private static final String PATH = "/admin/collection";
private static final String COLLECTION = "collection";
private static final String SHOW = "showStatus";
private static final String TRIGGER = "trigger";
@Autowired
private TdsContext tdsContext;
@PostConstruct
public void afterPropertiesSet() {
//this.tdsContext = _tdsContext;
DebugController.Category debugHandler = DebugController.find("Collections");
DebugController.Action act;
act = new DebugController.Action("showCollection", "Show Collections") {
public void doAction(DebugController.Event e) {
// get sorted list of collections
List<InvDatasetFeatureCollection> fcList = DataRootHandler.getInstance().getFeatureCollections();
Collections.sort(fcList, new Comparator<InvDatasetFeatureCollection>() {
public int compare(InvDatasetFeatureCollection o1, InvDatasetFeatureCollection o2) {
return o1.getName().compareTo(o2.getName());
}
});
for (InvDatasetFeatureCollection fc : fcList) {
String uriParam = Escape.uriParam(fc.getCollectionName());
String url = tdsContext.getContextPath() + PATH + "?" + COLLECTION + "=" + uriParam;
e.pw.printf("<p/><a href='%s'>%s</a>%n", url, fc.getName());
FeatureCollectionConfig config = fc.getConfig();
if (config != null)
e.pw.printf("%s%n",config.spec);
}
String url = tdsContext.getContextPath() + PATH + "/" + SHOW;
e.pw.printf("<p/><a href='%s'>Show All Collection Status</a>%n", url);
}
};
debugHandler.addAction(act);
act = new DebugController.Action("sched", "Show scheduler") {
public void doAction(DebugController.Event e) {
org.quartz.Scheduler scheduler = CollectionUpdater.INSTANCE.getScheduler();
if (scheduler == null) return;
try {
e.pw.println(scheduler.getMetaData());
List<String> groups = scheduler.getJobGroupNames();
List<String> triggers = scheduler.getTriggerGroupNames();
// enumerate each job group
for (String group : scheduler.getJobGroupNames()) {
e.pw.println("Group " + group);
// enumerate each job in group
for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.<JobKey>groupEquals(group))) {
e.pw.println(" Job " + jobKey.getName());
e.pw.println(" " + scheduler.getJobDetail(jobKey));
}
// enumerate each trigger in group
for (TriggerKey triggerKey : scheduler.getTriggerKeys(GroupMatcher.<TriggerKey>groupEquals(group))) {
e.pw.println(" Trigger " + triggerKey.getName());
e.pw.println(" " + scheduler.getTrigger(triggerKey));
}
}
} catch (Exception e1) {
e.pw.println("Error on scheduler " + e1.getMessage());
log.error("Error on scheduler " + e1.getMessage());
}
}
};
debugHandler.addAction(act);
act = new DebugController.Action("showFmrcCache", "Show FMRC Cache") {
public void doAction(DebugController.Event e) {
e.pw.println("<p>cache location = "+monitor.getCacheLocation()+"<p>");
String statUrl = tdsContext.getContextPath() + FMRC_PATH + "/"+STATISTICS;
e.pw.println("<p/> <a href='" + statUrl + "'>Show Cache Statistics</a>");
for (String name : monitor.getCachedCollections()) {
String ename = StringUtil2.quoteHtmlContent(name);
String url = tdsContext.getContextPath() + FMRC_PATH + "?"+COLLECTION+"="+ename;
e.pw.println("<p/> <a href='" + url + "'>" + name + "</a>");
}
}
};
debugHandler.addAction(act);
act = new DebugController.Action("syncFmrcCache", "Flush FMRC Cache to disk") {
public void doAction(DebugController.Event e) {
monitor.sync();
e.pw.println("<p>bdb cache location = "+monitor.getCacheLocation()+"<p> flushed to disk");
}
};
debugHandler.addAction(act);
}
@RequestMapping(value={"/collection/showStatus"})
protected ModelAndView handleCollectionStatus(HttpServletRequest req, HttpServletResponse res) throws Exception {
res.setContentType(ContentType.html.getContentHeader());
PrintWriter pw = res.getWriter();
// get sorted list of collections
List<InvDatasetFeatureCollection> fcList = DataRootHandler.getInstance().getFeatureCollections();
Collections.sort(fcList, new Comparator<InvDatasetFeatureCollection>() {
public int compare(InvDatasetFeatureCollection o1, InvDatasetFeatureCollection o2) {
return o1.getName().compareTo(o2.getName());
}
});
for (InvDatasetFeatureCollection fc : fcList) {
String uriParam = Escape.uriParam(fc.getCollectionName());
String url = tdsContext.getContextPath() + PATH + "?" + COLLECTION + "=" + uriParam;
pw.printf("<p/><a href='%s'>%s</a>%n", url, fc.getName());
pw.printf("<pre>%s</pre>%n", fc.showStatusShort("txt"));
}
return null;
}
@RequestMapping(value={"/collection/showStatus.csv"})
protected ModelAndView handleCollectionStatusCsv(HttpServletRequest req, HttpServletResponse res) throws Exception {
res.setContentType(ContentType.csv.getContentHeader());
PrintWriter pw = res.getWriter();
// get sorted list of collections
List<InvDatasetFeatureCollection> fcList = DataRootHandler.getInstance().getFeatureCollections();
Collections.sort(fcList, new Comparator<InvDatasetFeatureCollection>() {
public int compare(InvDatasetFeatureCollection o1, InvDatasetFeatureCollection o2) {
int compareType = o1.getConfig().type.toString().compareTo(o1.getConfig().type.toString());
if (compareType != 0) return compareType;
return o1.getName().compareTo(o2.getName());
}
});
pw.printf("%s, %s, %s, %s, %s, %s, %s, %s%n", "collection", "type", "group", "nrecords", "ndups", "%", "nmiss", "%");
for (InvDatasetFeatureCollection fc : fcList) {
if (fc.getConfig().type != FeatureCollectionType.GRIB1 && fc.getConfig().type != FeatureCollectionType.GRIB2) continue;
pw.printf("%s", fc.showStatusShort("csv"));
}
return null;
}
@RequestMapping(value={"/collection", "/collection/trigger"})
protected ModelAndView handleCollectionTriggers(HttpServletRequest req, HttpServletResponse res) throws Exception {
//String path = req.getPathInfo();
String path = req.getServletPath();
if (path == null) path = "";
if (path.startsWith("/admin") )
path = path.substring("/admin".length(), path.length());
res.setContentType(ContentType.html.getContentHeader());
PrintWriter pw = res.getWriter();
// deal with a trigger
CollectionUpdateType triggerType = null;
if (path.equals("/"+COLLECTION+"/"+TRIGGER)) {
String triggerTypeS = req.getParameter(TRIGGER);
try {
triggerType = CollectionUpdateType.valueOf(triggerTypeS);
} catch (Throwable t) {
; // noop
}
if (triggerType == null) {
res.setStatus(HttpServletResponse.SC_BAD_REQUEST);
pw.printf(" TRIGGER Type %s not legal%n", Escape.html(triggerTypeS));
pw.flush();
return null;
}
}
String collectName = StringUtil2.unescape( req.getParameter(COLLECTION)); // this is the collection name
InvDatasetFeatureCollection fc = DataRootHandler.getInstance().findFcByCollectionName(collectName);
if (fc == null) {
res.setStatus(HttpServletResponse.SC_NOT_FOUND);
pw.append("NOT FOUND");
pw.flush();
return null;
}
pw.printf("<h3>Collection %s</h3>%n", Escape.html(collectName));
if (triggerType != null) {
if (!fc.getConfig().isTrigggerOk()) {
res.setStatus(HttpServletResponse.SC_FORBIDDEN);
pw.printf(" TRIGGER NOT ENABLED%n");
pw.flush();
return null;
}
CollectionUpdater.INSTANCE.triggerUpdate(collectName, triggerType);
pw.printf(" TRIGGER SENT%n");
} else {
showFeatureCollection(pw, fc);
String uriParam = Escape.uriParam(fc.getCollectionName());
String url = tdsContext.getContextPath() + PATH + "/trigger?" + COLLECTION + "=" + uriParam + "&" + TRIGGER + "=" + CollectionUpdateType.nocheck;
pw.printf("<p/><a href='%s'>Send trigger to %s</a>%n", url, Escape.html(fc.getName()));
}
pw.flush();
return null;
}
private void showFeatureCollection(PrintWriter pw, InvDatasetFeatureCollection fc) {
FeatureCollectionConfig config = fc.getConfig(); // LOOK
if (config != null) {
Formatter f = new Formatter();
config.show(f);
pw.printf("%n<pre>%s%n</pre>", f.toString());
}
Formatter f = new Formatter();
fc.showStatus(f);
pw.printf("%n<pre>%s%n</pre>", f.toString());
}
/////////////////////////////////////////////////////////////
// old FmrcController - deprecated
private static final String FMRC_PATH = "/admin/showFmrc";
private static final String STATISTICS = "cacheStatistics.txt";
private static final String CMD = "cmd";
private static final String FILE = "file";
private final FmrcCacheMonitorImpl monitor = new FmrcCacheMonitorImpl();
@RequestMapping(value={"/showFmrc", "/showFmrc/*"})
protected ModelAndView showFmrcCache(HttpServletRequest req, HttpServletResponse res) throws Exception {
String path = TdsPathUtils.extractPath(req, "admin/"); // LOOK probably wrong
if (path.endsWith(STATISTICS)) {
res.setContentType(ContentType.text.getContentHeader());
Formatter f = new Formatter();
monitor.getCacheStatistics(f);
String s = f.toString();
PrintWriter pw = res.getWriter();
pw.println(s);
pw.flush();
return null;
}
String collectName = StringUtil2.unescape( req.getParameter(COLLECTION)); // this is the collection name
String fileName = req.getParameter(FILE);
String cmd = req.getParameter(CMD);
// show the file
if (fileName != null) {
String ufilename = java.net.URLDecoder.decode(fileName, CDM.UTF8);
String contents = monitor.getCachedFile(collectName, ufilename);
if (null == contents) {
res.setContentType(ContentType.html.getContentHeader());
PrintWriter pw = res.getWriter();
pw.println("<p/> Cant find filename="+Escape.html(fileName)+" in collection = "+ Escape.html(collectName));
} else {
res.setContentType(ContentType.xml.getContentHeader());
PrintWriter pw = res.getWriter();
pw.println(contents);
}
return null;
}
// list the collection
if (collectName != null) {
String ecollectName = Escape.uriParam(collectName);
String url = tdsContext.getContextPath() + FMRC_PATH + "?"+COLLECTION+"="+ecollectName;
res.setContentType(ContentType.html.getContentHeader());
PrintWriter pw = res.getWriter();
pw.println("Files for collection = "+Escape.html(collectName)+"");
// allow delete
String deleteUrl = tdsContext.getContextPath() + FMRC_PATH + "?"+COLLECTION+"="+ecollectName+"&"+CMD+"=delete";
pw.println("<a href='" + deleteUrl + "'> Delete" + "</a>");
pw.println("<ol>");
for (String filename : monitor.getFilesInCollection(collectName)) {
String efileName = java.net.URLEncoder.encode(filename, CDM.UTF8);
pw.println("<li> <a href='" + url + "&"+FILE+"="+efileName + "'>" + filename + "</a>");
}
pw.println("</ol>");
}
if (cmd != null && cmd.equals("delete")) {
res.setContentType(ContentType.html.getContentHeader());
PrintWriter pw = res.getWriter();
try {
monitor.deleteCollection(collectName);
pw.println("<p/>deleted");
} catch (Exception e) {
pw.println("<pre>delete failed on collection = "+Escape.html(collectName));
e.printStackTrace(pw);
pw.println("</pre>");
}
}
return null;
}
}