/*
* Gap Data
* Copyright (C) 2009 John Pritchard
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package gap.service;
import gap.*;
import gap.data.*;
import gap.hapax.*;
import gap.util.*;
import com.google.appengine.api.capabilities.CapabilitiesService;
import com.google.appengine.api.capabilities.Capability;
import com.google.appengine.api.capabilities.CapabilityState;
import com.google.appengine.api.capabilities.CapabilityStatus;
import static com.google.appengine.api.capabilities.CapabilityStatus.*;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.users.UserServiceFactory;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.security.Principal;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.LogRecord;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This core servlet extends the java http servlet with frequently
* used GAE/GD features.
*
* It also serves the special files "/version.txt" (gap data version)
* and "/system.txt" (system properties). System txt service may
* disabled by an override in a subclass, however version txt service
* is intended common to all instances.
*/
public class Servlet
extends javax.servlet.http.HttpServlet
implements Service,
gap.data.DataInheritance.Notation
{
/**
* Initialize configured servlet classes.
*/
public final static gap.util.Services Services = (new gap.util.Services(Servlet.class)).init();
static {
gap.data.BigTable.Services.init();
}
/**
* Email address handling
*/
public final static class Email {
private final static Pattern EMAILADDR = Pattern.compile("[\\w\\-\\.]+@[\\w\\-\\.]+", Pattern.CASE_INSENSITIVE);
/**
* @param string Input
* @return Non null for recognized "valid" email address
*/
public final static String Clean(String string){
if (null == string || 0 == string.length())
return null;
else {
Matcher m = EMAILADDR.matcher(string);
if (m.matches())
return string;
else
return null;
}
}
/**
* @param string Input
* @return Recognized "valid" email address
*/
public final static boolean IsValid(String string){
if (null == string || 0 == string.length())
return false;
else {
Matcher m = EMAILADDR.matcher(string);
return (m.matches());
}
}
}
/**
* Universal charset
*/
public final static Charset UTF8 = Charset.forName("UTF-8");
protected final static Logger Log = Logger.getLogger(Servlet.class.getName());
/**
* One servlet config in the classloader scope.
*/
protected volatile static ServletConfig Config;
/**
* Error output structure
*/
public final static class TemplateNames {
public final static TemplateName Error = new TemplateName("error");
public final static TemplateName ErrorException = new TemplateName("error_exception");
public final static TemplateName ErrorExceptionJson = new TemplateName("error_exception_json");
public final static TemplateName ErrorMessage = new TemplateName("error_message");
public final static TemplateName ErrorMessageJson = new TemplateName("error_message_json");
public final static TemplateName ErrorStatus = new TemplateName("error_status");
public final static TemplateName ErrorType = new TemplateName("error_type");
public final static TemplateName ErrorUri = new TemplateName("error_uri");
}
public Servlet(){
super();
}
public String getMimeType(String filename){
return this.getServletConfig().getServletContext().getMimeType(filename);
}
/**
* @return May be null
*/
public final String getAppsDomain(){
String re = com.google.appengine.api.NamespaceManager.getGoogleAppsNamespace();
if (null == re || 0 == re.length())
return null;
else
return re;
}
@Override
public void init(ServletConfig config) throws ServletException {
if (null == Config)
Config = config;
super.init(config);
}
/**
* This method injects store enter and exit, and defines an
* efficient servlet processor for this package.
*/
@Override
public final void service(ServletRequest req, ServletResponse rep)
throws IOException, ServletException
{
this.service( (HttpServletRequest)req, (HttpServletResponse)rep);
}
/**
* This is marked final for performance, and for the integrity of
* the stats process.
*/
@Override
protected final void service(HttpServletRequest req, HttpServletResponse rep)
throws IOException, ServletException
{
final String ns = this.getAppsDomain();
Service.Routines.Enter(ns);
Method method = Method.Enter(req);
Protocol protocol = Protocol.Enter(req);
Path path = new Path(req);
Accept accept = new Accept(req);
FileManager fm = new FileManager(path.getComponent(0));
String uri = req.getParameter("uri");
if (null == uri){
uri = req.getRequestURI();
}
Request request = null;
Response response = null;
try {
Logon logon = Logon.Enter(new Logon(ns,req.getUserPrincipal(),uri,UserServiceFactory.getUserService()));
request = this.createRequest(ns,req,method,protocol,path,accept,fm,logon,uri);
response = this.createResponse(request,rep);
if (null != response)
this.service(request, response);
}
catch (Exception any){
LogRecord rec = new LogRecord(Level.SEVERE,"error");
rec.setThrown(any);
Log.log(rec);
if (null != request){
if (null != response)
this.error(request,response,500,"Internal error",any);
else
this.error(request,new Response(rep),500,"Internal error",any);
}
else
this.error(req,rep,500,"Internal error",any);
}
finally {
Service.Routines.Exit();
}
}
protected boolean serviceSystemTxt(){
return true;
}
/**
* This is marked final for performance, and thinking it's
* unlikely anyone would want to override it.
*/
protected final void service(Request req, Response rep)
throws IOException, ServletException, TemplateException
{
Servlet servlet = req.getServlet();
switch (req.method.type){
case Method.GET:{
long lastModified = this.getLastModified(req);
if (0 < lastModified){
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (0 < ifModifiedSince){
lastModified /= 1000;
lastModified *= 1000;
if (ifModifiedSince < lastModified)
MaybeSetLastModified(rep, lastModified);
else {
rep.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
}
}
if (null != servlet){
servlet.doGet(req,rep);
return;
}
else if (req.isPath("/version.txt")){
PrintWriter out = rep.getWriter();
VersionTxt(out);
rep.setContentTypeText();
return;
}
else if (req.isPath("/system.txt") && this.serviceSystemTxt()){
PrintWriter out = rep.getWriter();
SystemTxt(out);
rep.setContentTypeText();
return;
}
else {
this.doGet(req,rep);
return;
}
}
case Method.HEAD:{
long lastModified = this.getLastModified(req);
if (0 < lastModified){
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (0 < ifModifiedSince){
lastModified /= 1000;
lastModified *= 1000;
if (ifModifiedSince < lastModified)
MaybeSetLastModified(rep, lastModified);
else {
rep.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
}
}
rep.openDevNull();
try {
if (null != servlet)
servlet.doGet(req,rep);
else if (req.isPath("/version.txt")){
PrintWriter out = rep.getWriter();
VersionTxt(out);
rep.setContentTypeText();
}
else if (req.isPath("/system.txt") && this.serviceSystemTxt()){
PrintWriter out = rep.getWriter();
SystemTxt(out);
rep.setContentTypeText();
}
else
this.doGet(req,rep);
}
finally {
rep.setContentLength();
}
return;
}
case Method.POST:
if (null != servlet)
servlet.doPost(req,rep);
else
this.doPost(req,rep);
return;
case Method.PUT:
if (null != servlet)
servlet.doPut(req,rep);
else
this.doPut(req,rep);
return;
case Method.DELETE:
if (null != servlet)
servlet.doDelete(req,rep);
else
this.doDelete(req,rep);
return;
case Method.OPTIONS:
if (null != servlet)
servlet.doOptions(req,rep);
else
this.doOptions(req,rep);
return;
case Method.TRACE:
if (null != servlet)
servlet.doTrace(req,rep);
else
this.doTrace(req,rep);
return;
default:
if (null != servlet)
servlet.doMethod(req,rep);
else
this.doMethod(req,rep);
return;
}
}
/**
* @return Template for request servlet sub path excluding servlet
* path.
*/
protected TemplateRenderer getTemplate(Request req)
throws TemplateException
{
return req.getTemplate();
}
protected void doGet(Request req, Response rep)
throws ServletException, IOException
{
if (req.hasNotSource()){
if (req.acceptHtml())
rep.sendRedirect("/index.html");
else
this.error(req,rep,404,"Not found");
}
else
this.render(req,rep);
}
protected void doPost(Request req, Response rep)
throws ServletException, IOException
{
this.undefined(req,rep);
}
protected void doPut(Request req, Response rep)
throws ServletException, IOException
{
this.undefined(req,rep);
}
protected void doDelete(Request req, Response rep)
throws ServletException, IOException
{
this.undefined(req,rep);
}
protected void doOptions(Request req, Response rep)
throws ServletException, IOException
{
this.undefined(req,rep);
}
protected void doTrace(Request req, Response rep)
throws ServletException, IOException
{
this.undefined(req,rep);
}
protected void doMethod(Request req, Response rep)
throws IOException, ServletException
{
this.undefined(req,rep);
}
protected final void ok(HttpServletRequest req, HttpServletResponse rep)
throws IOException, ServletException
{
rep.setStatus(200,"Ok");
}
protected final void er(HttpServletRequest req, HttpServletResponse rep, int status, String message)
throws IOException, ServletException
{
rep.setStatus(status,message);
}
protected final void log(HttpServletRequest req, Level level, String message)
throws IOException, ServletException
{
Log.log(Level.INFO,alto.io.u.Chbuf.fcat(req.getServletPath(),req.getPathInfo()),message);
}
protected final void log(HttpServletRequest req, Level level, Throwable t)
throws IOException, ServletException
{
Log.log(Level.INFO,alto.io.u.Chbuf.fcat(req.getServletPath(),req.getPathInfo()),t);
}
protected final void info(HttpServletRequest req, String message)
throws IOException, ServletException
{
this.log(req,Level.INFO,message);
}
protected final void info(HttpServletRequest req, Throwable t)
throws IOException, ServletException
{
this.log(req,Level.INFO,t);
}
protected final void error(HttpServletRequest req, HttpServletResponse rep)
throws IOException, ServletException
{
this.error(req, rep, 0, null, null);
}
protected final void error(HttpServletRequest req, HttpServletResponse rep, int status, String statusMessage)
throws IOException, ServletException
{
this.error(req, rep, status, statusMessage, null);
}
protected final void error(HttpServletRequest req, HttpServletResponse rep, int status, String statusMessage, Throwable any)
throws IOException, ServletException
{
try {
rep.resetBuffer();
if (this.isAvailableStorageRead() &&
req instanceof Request && rep instanceof Response &&
((Request)req).acceptHtml())
{
Request request = (Request)req;
TemplateDataDictionary top = request;
TemplateDataDictionary error = top.addSection(TemplateNames.Error);
String errors_exception = Error.Attribute.ToString.Exception(req,any);
String errors_status, errors_message;
String errors_type = Error.Attribute.ToString.Type(req,any);
String errors_uri = Error.Attribute.ToString.URI(req,any);
if (0 < status){
rep.setStatus(status,statusMessage);
errors_status = String.valueOf(status);
errors_message = statusMessage;
}
else {
errors_status = Error.Attribute.ToString.Status(req,any);
errors_message = Error.Attribute.ToString.Message(req,any);
}
error.setVariable(TemplateNames.ErrorMessage,errors_message);
error.setVariable(TemplateNames.ErrorException,errors_exception);
error.setVariable(TemplateNames.ErrorStatus,errors_status);
error.setVariable(TemplateNames.ErrorType,errors_type);
error.setVariable(TemplateNames.ErrorUri,errors_uri);
String templateName = null;
if (request.accept("text/html"))
templateName = "errors.html";
else if (request.accept("application/json")){
error.setVariable(TemplateNames.ErrorExceptionJson,QuoteJson(errors_exception));
error.setVariable(TemplateNames.ErrorMessageJson,QuoteJson(errors_message));
templateName = "errors.json";
}
else if (request.accept("text/xml"))
templateName = "errors.xml";
else if (request.accept("application/xml"))
templateName = "errors.xml";
if (null != templateName){
try {
TemplateRenderer template = Templates.GetTemplate(templateName);
if (null != template)
this.render(request, ((Response)rep), template);
}
catch (TemplateException exc){
LogRecord rec = new LogRecord(Level.SEVERE,"error");
rec.setThrown(exc);
Log.log(rec);
}
}
}
else if (0 < status)
rep.setStatus(status,statusMessage);
}
catch (Exception exc){
Log.log(Level.INFO,alto.io.u.Chbuf.fcat(req.getServletPath(),req.getPathInfo()),exc);
rep.setStatus(status,statusMessage);
}
}
protected final void undefined(Request req, Response rep)
throws ServletException, IOException
{
this.error(req,rep,HttpServletResponse.SC_NOT_IMPLEMENTED, "Method '"+req.getMethod()+"' not implemented");
}
/**
* Render template for request servlet sub path (excluding servlet
* path).
*
* @param req Servlet request
* @param rep Servlet response
*/
protected final void render(Request req, Response rep)
throws IOException, ServletException, TemplateException
{
try {
TemplateRenderer template = this.getTemplate(req);
if (null != template){
this.render(req,rep,template);
}
else {
this.error(req,rep,404,"Not found");
}
}
catch (TemplateException exc){
LogRecord rec = new LogRecord(Level.SEVERE,req.userReference);
rec.setThrown(exc);
Servlet.Log.log(rec);
this.error(req,rep,500,"Template error",exc);
}
}
/**
* Render named template
*
* @param req Servlet request
* @param rep Servlet response
*
* @param templateName Path relative to
* <code>"WEB-INF/templates"</code> (optionally includes a
* trailing <code>".xtm"</code> file name suffix).
*/
protected final void render(Request req, Response rep, String templateName)
throws IOException, ServletException, TemplateException
{
TemplateRenderer template = Templates.GetTemplate(templateName);
if (null != template)
this.render(req,rep,template);
else
this.error(req,rep,500,"Template not found '"+templateName+"'");
}
protected final void render(Request req, Response rep, TemplateRenderer template)
throws IOException, ServletException, TemplateException
{
try {
template.render(req,rep.getWriter());
if (null == rep.getContentType()){
if (req.accept("text/html"))
rep.setContentType("text/html;charset=utf-8");
else if (req.accept("application/json"))
rep.setContentType("application/json");
else if (req.accept("text/xml"))
rep.setContentType("text/xml");
else if (req.accept("application/xml"))
rep.setContentType("application/xml");
}
}
catch (gap.hapax.TemplateParser.Retry exc){
rep.resetBuffer();
/*
*
*/
template.render(req,rep.getWriter());
if (null == rep.getContentType()){
if (req.accept("text/html"))
rep.setContentType("text/html;charset=utf-8");
else if (req.accept("application/json"))
rep.setContentType("application/json");
else if (req.accept("text/xml"))
rep.setContentType("text/xml");
else if (req.accept("application/xml"))
rep.setContentType("application/xml");
}
}
finally {
req.renderComplete();
}
}
protected void redirectToItem(Request req, Response rep, String id)
throws IOException, ServletException
{
String path = '/'+id;
if (req.userReference.endsWith(path))
rep.sendRedirect(req.userReference);
else if (req.hasBase()){
String base = req.getBase();
rep.sendRedirect("/"+base+path);
}
else
rep.sendRedirect(path);
}
protected Parameters createParameters(HttpServletRequest req, Path path){
Class<? extends gap.data.TableClass> table = null;
{
if (path.hasKind(0))
table = path.getKind(0).getTableClass();
}
return new Parameters(req,Page.DefaultCount,table);
}
/**
* Must not throw an exception. May only return a null value when
* the subsequent call to create response will return a null
* value -- otherwise -- must not return a null value.
*/
protected Request createRequest(String ns, HttpServletRequest req, Method method, Protocol protocol, Path path,
Accept accept, FileManager fm, Logon logon, String uri)
{
Parameters parameters = this.createParameters(req,path);
return new Request(ns,req,method,protocol,path,accept,fm,logon,uri,parameters);
}
/**
* May return null to halt further processing.
*/
protected Response createResponse(Request req, HttpServletResponse rep){
return new Response(rep);
}
public final boolean isAvailableStorageRead(){
return (this.isAvailableDatastore() && this.isAvailableMemcache());
}
public final boolean isAvailableStorageWrite(){
return (this.isAvailableDatastoreWrite() && this.isAvailableMemcache());
}
public final boolean isAvailableBlobstore(){
return IsAvailable(Capability.BLOBSTORE);
}
public final java.util.Date getScheduledBlobstore(){
return GetScheduled(Capability.BLOBSTORE);
}
public final boolean isAvailableDatastore(){
return IsAvailable(Capability.DATASTORE);
}
public final java.util.Date getScheduledDatastore(){
return GetScheduled(Capability.DATASTORE);
}
public final boolean isAvailableDatastoreWrite(){
return IsAvailable(Capability.DATASTORE_WRITE);
}
public final java.util.Date getScheduledDatastoreWrite(){
return GetScheduled(Capability.DATASTORE_WRITE);
}
public final boolean isAvailableImages(){
return IsAvailable(Capability.IMAGES);
}
public final java.util.Date getScheduledImages(){
return GetScheduled(Capability.IMAGES);
}
public final boolean isAvailableMail(){
return IsAvailable(Capability.MAIL);
}
public final java.util.Date getScheduledMail(){
return GetScheduled(Capability.MAIL);
}
public final boolean isAvailableProspectiveSearch(){
return IsAvailable(Capability.PROSPECTIVE_SEARCH);
}
public final java.util.Date getScheduledProspectiveSearch(){
return GetScheduled(Capability.PROSPECTIVE_SEARCH);
}
public final boolean isAvailableMemcache(){
return IsAvailable(Capability.MEMCACHE);
}
public final java.util.Date getScheduledMemcache(){
return GetScheduled(Capability.MEMCACHE);
}
public final boolean isAvailableTaskqueue(){
return IsAvailable(Capability.TASKQUEUE);
}
public final java.util.Date getScheduledTaskqueue(){
return GetScheduled(Capability.TASKQUEUE);
}
public final boolean isAvailableUrlFetch(){
return IsAvailable(Capability.URL_FETCH);
}
public final java.util.Date getScheduledUrlFetch(){
return GetScheduled(Capability.URL_FETCH);
}
public final boolean isAvailableXmpp(){
return IsAvailable(Capability.XMPP);
}
public final java.util.Date getScheduledXmpp(){
return GetScheduled(Capability.XMPP);
}
/*
*/
protected final static String Safe(String string){
if (null == string)
return "";
else
return string;
}
protected final static String HEADER_IFMODSINCE = "If-Modified-Since";
protected final static String HEADER_LASTMOD = "Last-Modified";
protected final static void MaybeSetLastModified(HttpServletResponse rep,
long lastModified)
{
if (rep.containsHeader(HEADER_LASTMOD))
return;
else if (0 <= lastModified)
rep.setDateHeader(HEADER_LASTMOD, lastModified);
}
/**
*/
protected final static String QuoteXml(String string){
if (null == string)
return null;
else {
StringBuilder re = new StringBuilder();
char[] cary = string.toCharArray();
for (int cc = 0, count = cary.length; cc < count; cc++){
char ch = cary[cc];
switch (ch){
case '<':
re.append("<");
break;
case '&':
re.append("&");
break;
case '>':
re.append(">");
break;
default:
re.append(ch);
break;
}
}
return re.toString();
}
}
/**
*/
protected final static String QuoteJson(String string){
if (null == string)
return null;
else {
StringBuilder re = new StringBuilder();
char[] cary = string.toCharArray();
for (int cc = 0, count = cary.length; cc < count; cc++){
char ch = cary[cc];
switch (ch){
case '\b':
re.append("\\b");
break;
case '\r':
re.append("\\r");
break;
case '\n':
re.append("\\n");
break;
case '\t':
re.append("\\t");
break;
case '\f':
re.append("\\f");
break;
case '"':
re.append("\\\"");
break;
case '\\':
re.append("\\\\");
break;
case '/':
re.append("\\/");
break;
case 0x2028:
case 0x2029:
break;
default:
int code = ch;
if ((0x20 > code)||(0x7e < code && 0xa0 > code))
break;
else if (0x7e < code){
re.append("\\u")
.append(HEX[(code >>> 12) & 0xf])
.append(HEX[(code >>> 8) & 0xf])
.append(HEX[(code >>> 4) & 0xf])
.append(HEX[code & 0xf]);
break;
}
else {
re.append(ch);
break;
}
}
}
return re.toString();
}
}
private static final char[] HEX = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
protected final static void VersionTxt(PrintWriter out){
out.printf("%s/%s%n",gap.Version.Name,gap.Version.Long);
}
protected final static void SystemTxt(PrintWriter out){
try {
for (java.util.Map.Entry ent: System.getProperties().entrySet()){
String key = (String)ent.getKey();
String value = (String)ent.getValue();
out.printf("%s: %s%n",key,value);
}
}
catch (Throwable t){
out.println();
t.printStackTrace(out);
}
}
private static CapabilitiesService CapService;
protected final static CapabilitiesService CapService(){
if (null == CapService){
CapService = com.google.appengine.api.capabilities.CapabilitiesServiceFactory.getCapabilitiesService();
}
return CapService;
}
protected final static CapabilityState GetState(Capability cap){
return CapService().getStatus(cap);
}
/**
* @return True for not disabled
*/
protected final static boolean IsAvailable(Capability cap){
CapabilityState state = GetState(cap);
CapabilityStatus status = state.getStatus();
switch(status){
case DISABLED:
return false;
case ENABLED:
case SCHEDULED_MAINTENANCE:
return true;
default:
throw new IllegalStateException(status.name());
}
}
/**
* @return Null for not scheduled, or the planned maintenance date
*/
protected final static java.util.Date GetScheduled(Capability cap){
CapabilityState state = GetState(cap);
CapabilityStatus status = state.getStatus();
switch(status){
case DISABLED:
case ENABLED:
return null;
case SCHEDULED_MAINTENANCE:
return state.getScheduledDate();
default:
throw new IllegalStateException(status.name());
}
}
public final static boolean IsAvailableStorageRead(){
return (IsAvailableDatastore() && IsAvailableMemcache());
}
public final static boolean IsAvailableStorageWrite(){
return (IsAvailableDatastoreWrite() && IsAvailableMemcache());
}
public final static boolean IsAvailableBlobstore(){
return IsAvailable(Capability.BLOBSTORE);
}
public final static java.util.Date GetScheduledBlobstore(){
return GetScheduled(Capability.BLOBSTORE);
}
public final static boolean IsAvailableDatastore(){
return IsAvailable(Capability.DATASTORE);
}
public final static java.util.Date GetScheduledDatastore(){
return GetScheduled(Capability.DATASTORE);
}
public final static boolean IsAvailableDatastoreWrite(){
return IsAvailable(Capability.DATASTORE_WRITE);
}
public final static java.util.Date GetScheduledDatastoreWrite(){
return GetScheduled(Capability.DATASTORE_WRITE);
}
public final static boolean IsAvailableImages(){
return IsAvailable(Capability.IMAGES);
}
public final static java.util.Date GetScheduledImages(){
return GetScheduled(Capability.IMAGES);
}
public final static boolean IsAvailableMail(){
return IsAvailable(Capability.MAIL);
}
public final static java.util.Date GetScheduledMail(){
return GetScheduled(Capability.MAIL);
}
public final static boolean IsAvailableProspectiveSearch(){
return IsAvailable(Capability.PROSPECTIVE_SEARCH);
}
public final static java.util.Date GetScheduledProspectiveSearch(){
return GetScheduled(Capability.PROSPECTIVE_SEARCH);
}
public final static boolean IsAvailableMemcache(){
return IsAvailable(Capability.MEMCACHE);
}
public final static java.util.Date GetScheduledMemcache(){
return GetScheduled(Capability.MEMCACHE);
}
public final static boolean IsAvailableTaskqueue(){
return IsAvailable(Capability.TASKQUEUE);
}
public final static java.util.Date GetScheduledTaskqueue(){
return GetScheduled(Capability.TASKQUEUE);
}
public final static boolean IsAvailableUrlFetch(){
return IsAvailable(Capability.URL_FETCH);
}
public final static java.util.Date GetScheduledUrlFetch(){
return GetScheduled(Capability.URL_FETCH);
}
public final static boolean IsAvailableXmpp(){
return IsAvailable(Capability.XMPP);
}
public final static java.util.Date GetScheduledXmpp(){
return GetScheduled(Capability.XMPP);
}
/*
* Convenient utilities as for data beans (from BigTable)
*/
public final static boolean IsEqual(Key a, Key b){
if (null == a)
return (null == b);
else if (null == b)
return false;
else if (a.getKind().equals(b.getKind())){
String na = a.getName();
String nb = b.getName();
if (null != na && null != nb && na.equals(nb)){
return IsEqual(a.getParent(),b.getParent());
}
}
return false;
}
public final static boolean IsEqual(Object a, Object b){
if (a == b)
return true;
else if (null == a || null == b)
return false;
else
return (a.equals(b));
}
public final static boolean IsNotEqual(Object a, Object b){
if (a == b)
return false;
else if (null == a || null == b)
return true;
else
return (!a.equals(b));
}
public final static boolean IsTrue(Boolean bool){
if (null == bool)
return false;
else
return bool;
}
public final static boolean IsNotTrue(Boolean bool){
if (null == bool)
return true;
else
return (!bool);
}
}