https://github.com/schemaorg/schemaorg
Raw File
Tip revision: 19475ebab339054918db8f526af68b46cbdbdd29 authored by Dan Brickley on 28 April 2016, 11:56:09 UTC
Fix a bug with Hospital, Dentist definitions.
Tip revision: 19475eb
sdoapp.py
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import os
import re
import webapp2
import jinja2
import logging
import StringIO
import json
from apirdflib import load_graph, getNss, getRevNss

from markupsafe import Markup, escape # https://pypi.python.org/pypi/MarkupSafe

import parsers
import threading
import itertools
import datetime, time
from time import gmtime, strftime

from google.appengine.ext import ndb
from google.appengine.ext import blobstore
from google.appengine.api import users
from google.appengine.ext.webapp import blobstore_handlers
from google.appengine.api import modules
from google.appengine.api import runtime

from api import inLayer, read_file, full_path, read_schemas, read_extensions, read_examples, namespaces, DataCache
from api import Unit, GetTargets, GetSources, GetComments, GetsoftwareVersions
from api import GetComment, all_terms, GetAllTypes, GetAllProperties, GetAllEnumerationValues, GetAllTerms, LoadExamples
from api import GetParentList, GetImmediateSubtypes, HasMultipleBaseTypes
from api import GetJsonLdContext, ShortenOnSentence, StripHtmlTags, MD
from api import setInTestHarness, getInTestHarness, setAllLayersList

logging.basicConfig(level=logging.INFO) # dev_appserver.py --log_level debug .
log = logging.getLogger(__name__)

SCHEMA_VERSION=3.0

FEEDBACK_FORM_BASE_URL='https://docs.google.com/a/google.com/forms/d/1krxHlWJAO3JgvHRZV9Rugkr9VYnMdrI10xbGsWt733c/viewform?entry.1174568178&entry.41124795={0}&entry.882602760={1}'
# {0}: term URL, {1} category of term.

sitemode = "mainsite" # whitespaced list for CSS tags,
            # e.g. "mainsite testsite" when off expected domains
            # "extensionsite" when in an extension (e.g. blue?)

releaselog = { "2.0": "2015-05-13", "2.1": "2015-08-06", "2.2": "2015-11-05" }
#

silent_skip_list =  [ "favicon.ico" ] # Do nothing for now

all_layers = {}
ext_re = re.compile(r'([^\w,])+')
PageCache = {}

#TODO: Modes:
# mainsite
# webschemadev
# known extension (not skiplist'd, eg. demo1 on schema.org)

JINJA_ENVIRONMENT = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.join(os.path.dirname(__file__), 'templates')),
    extensions=['jinja2.ext.autoescape'], autoescape=True, cache_size=0)

ENABLE_JSONLD_CONTEXT = True
ENABLE_CORS = True
ENABLE_HOSTED_EXTENSIONS = True

#INTESTHARNESS = True #Used to indicate we are being called from tests - use setInTestHarness() & getInTestHarness() to manage value

EXTENSION_SUFFIX = "" # e.g. "*"

ENABLED_EXTENSIONS = ['auto', 'bib', 'health-lifesci', 'pending', 'meta'  ]
ALL_LAYERS = ['core',""]

ALL_LAYERS += ENABLED_EXTENSIONS
setAllLayersList(ALL_LAYERS)


FORCEDEBUGGING = False
# FORCEDEBUGGING = True

SHAREDSITEDEBUG = True
if getInTestHarness():
    SHAREDSITEDEBUG = False
############# Warmup Control ########
WarmedUp = False
WarmupState = "Auto"
if "WARMUPSTATE" in os.environ:
    WarmupState = os.environ["WARMUPSTATE"]
log.info("WarmupState: %s" % WarmupState)

if WarmupState.lower() == "off":
    WarmedUp = True
elif "SERVER_NAME" in os.environ and ("localhost" in os.environ['SERVER_NAME'] and WarmupState.lower() == "auto"):
    WarmedUp = True
######################################

############# Shared values and times ############
#### Memcache functions dissabled in test mode ###
appver = "TestHarness Version"
if "CURRENT_VERSION_ID" in os.environ:
    appver = os.environ["CURRENT_VERSION_ID"]
instance_first = True
instance_num = 0
callCount = 0
global_vars = threading.local()
starttime = datetime.datetime.utcnow()
systarttime = starttime

if not getInTestHarness():
    from google.appengine.api import memcache

def tick(): #Keep memcache values fresh so they don't expire
    if not getInTestHarness():
        memcache.set(key="SysStart", value=systarttime)
        memcache.set(key="static-version", value=appver)

#Ensure clean start for any memcached values...
if not getInTestHarness():
    if memcache.get("static-version") != appver: #We are a new instance of the app
        memcache.flush_all()
        memcache.set(key="static-version", value=appver)
        memcache.add(key="SysStart", value=systarttime)
        instance_first = True
        log.info("Detected new code version - resetting memory values")
    else:
       systarttime = memcache.get("SysStart")
       tick()

modtime = systarttime.replace(microsecond=0)
etagSlug = "24751%s" % modtime.strftime("%y%m%d%H%M%Sa")
#################################################

def cleanPath(node):
    """Return the substring of a string matching chars approved for use in our URL paths."""
    return re.sub(r'[^a-zA-Z0-9\-/,\.]', '', str(node), flags=re.DOTALL)



class HTMLOutput:
    """Used in place of http response when we're collecting HTML to pass to template engine."""

    def __init__(self):
        self.outputStrings = []

    def write(self, str):
        self.outputStrings.append(str)

    def toHTML(self):
        return Markup ( "".join(self.outputStrings)  )

    def __str__(self):
        return self.toHTML()

# Core API: we have a single schema graph built from triples and units.
# now in api.py


class TypeHierarchyTree:

    def __init__(self, prefix=""):
        self.txt = ""
        self.visited = {}
        self.prefix = prefix

    def emit(self, s):
        self.txt += s + "\n"

    def emit2buff(self, buff, s):
        buff.write(s + "\n")

    def toHTML(self):
        return '%s<ul>%s</ul>' % (self.prefix, self.txt)

    def toJSON(self):
        return self.txt

    def traverseForHTML(self, node, depth = 1, hashorslash="/", layers='core', buff=None):

        """Generate a hierarchical tree view of the types. hashorslash is used for relative link prefixing."""

        log.debug("traverseForHTML: node=%s hashorslash=%s" % ( node.id, hashorslash ))

        if node.superseded(layers=layers):
            return False

        localBuff = False
        if buff == None:
            localBuff = True
            buff = StringIO.StringIO()

        urlprefix = ""
        home = node.getHomeLayer()
        gotOutput = False
        if home in layers:
            gotOutput = True

        if home in ENABLED_EXTENSIONS and home != getHostExt():
            urlprefix = makeUrl(home)

        extclass = ""
        extflag = ""
        tooltip=""
        if home != "core" and home != "":
            extclass = "class=\"ext ext-%s\"" % home
            extflag = EXTENSION_SUFFIX
            tooltip = "title=\"Extended schema: %s.schema.org\" " % home

        # we are a supertype of some kind
        subTypes = node.GetImmediateSubtypes(layers=ALL_LAYERS)
        if len(subTypes) > 0:
            # and we haven't been here before
            if node.id not in self.visited:
                self.visited[node.id] = True # remember our visit
                self.emit2buff(buff, ' %s<li class="tbranch" id="%s"><a %s %s href="%s%s%s">%s</a>%s' % (" " * 4 * depth, node.id,  tooltip, extclass, urlprefix, hashorslash, node.id, node.id, extflag) )
                self.emit2buff(buff, ' %s<ul>' % (" " * 4 * depth))

                # handle our subtypes
                for item in subTypes:
                    subBuff = StringIO.StringIO()
                    got = self.traverseForHTML(item, depth + 1, hashorslash=hashorslash, layers=layers, buff=subBuff)
                    if got:
                        gotOutput = True
                        self.emit2buff(buff,subBuff.getvalue())
                    subBuff.close()
                self.emit2buff(buff, ' %s</ul>' % (" " * 4 * depth))
            else:
                # we are a supertype but we visited this type before, e.g. saw Restaurant via Place then via Organization
                seen = '  <a href="#%s">+</a> ' % node.id
                self.emit2buff(buff, ' %s<li class="tbranch" id="%s"><a %s %s href="%s%s%s">%s</a>%s%s' % (" " * 4 * depth, node.id,  tooltip, extclass, urlprefix, hashorslash, node.id, node.id, extflag, seen) )

        # leaf nodes
        if len(subTypes) == 0:
            if node.id not in self.visited:
                self.emit2buff(buff, '%s<li class="tleaf" id="%s"><a %s %s href="%s%s%s">%s</a>%s%s' % (" " * depth, node.id, tooltip, extclass, urlprefix, hashorslash, node.id, node.id, extflag, "" ))
            #else:
                #self.visited[node.id] = True # never...
                # we tolerate "VideoGame" appearing under both Game and SoftwareApplication
                # and would only suppress it if it had its own subtypes. Seems legit.

        self.emit2buff(buff, ' %s</li>' % (" " * 4 * depth) )

        if localBuff:
            self.emit(buff.getvalue())
            buff.close()

        return gotOutput

    # based on http://danbri.org/2013/SchemaD3/examples/4063550/hackathon-schema.js  - thanks @gregg, @sandro
    def traverseForJSONLD(self, node, depth = 0, last_at_this_level = True, supertype="None", layers='core'):
        emit_debug = False
        if node.id in self.visited:
            # self.emit("skipping %s - already visited" % node.id)
            return
        self.visited[node.id] = True
        p1 = " " * 4 * depth
        if emit_debug:
            self.emit("%s# @id: %s last_at_this_level: %s" % (p1, node.id, last_at_this_level))
        global namespaces;
        ctx = "{}".format(""""@context": {
    "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
    "schema": "http://schema.org/",
    "rdfs:subClassOf": { "@type": "@id" },
    "name": "rdfs:label",
    "description": "rdfs:comment",
    "children": { "@reverse": "rdfs:subClassOf" }
  },\n""" if last_at_this_level and depth==0 else '' )

        unseen_subtypes = []
        for st in node.GetImmediateSubtypes(layers=layers):
            if not st.id in self.visited:
                unseen_subtypes.append(st)
        unvisited_subtype_count = len(unseen_subtypes)
        subtype_count = len( node.GetImmediateSubtypes(layers=layers) )

        supertx = "{}".format( '"rdfs:subClassOf": "schema:%s", ' % supertype.id if supertype != "None" else '' )
        maybe_comma = "{}".format("," if unvisited_subtype_count > 0 else "")
        comment = GetComment(node, layers).strip()
        comment = ShortenOnSentence(StripHtmlTags(comment),60)

        def encode4json(s):
            return json.dumps(s)

        self.emit('\n%s{\n%s\n%s"@type": "rdfs:Class", %s "description": %s,\n%s"name": "%s",\n%s"@id": "schema:%s",\n%s"layer": "%s"%s'
                  % (p1, ctx, p1,                 supertx,            encode4json(comment),     p1,   node.id, p1,        node.id, p1, node.getHomeLayer(), maybe_comma))

        i = 1
        if unvisited_subtype_count > 0:
            self.emit('%s"children": ' % p1 )
            self.emit("  %s["  % p1 )
            inner_lastness = False
            for t in unseen_subtypes:
                if emit_debug:
                    self.emit("%s  # In %s > %s i: %s unvisited_subtype_count: %s" %(p1, node.id, t.id, i, unvisited_subtype_count))
                if i == unvisited_subtype_count:
                    inner_lastness = True
                i = i + 1
                self.traverseForJSONLD(t, depth + 1, inner_lastness, supertype=node, layers=layers)

            self.emit("%s  ]%s" % (p1,  "{}".format( "" if not last_at_this_level else '' ) ) )

        maybe_comma = "{}".format( ',' if not last_at_this_level else '' )
        self.emit('\n%s}%s\n' % (p1, maybe_comma))



def GetExamples(node, layers='core'):
    """Returns the examples (if any) for some Unit node."""
    return LoadExamples(node,layers)

def GetExtMappingsRDFa(node, layers='core'):
    """Self-contained chunk of RDFa HTML markup with mappings for this term."""
    if (node.isClass()):
        equivs = GetTargets(Unit.GetUnit("owl:equivalentClass"), node, layers=layers)
        if len(equivs) > 0:
            markup = ''
            for c in equivs:

                if (c.id.startswith('http')):
                  markup = markup + "<link property=\"owl:equivalentClass\" href=\"%s\"/>\n" % c.id
                else:
                  markup = markup + "<link property=\"owl:equivalentClass\" resource=\"%s\"/>\n" % c.id

            return markup
    if (node.isAttribute()):
        equivs = GetTargets(Unit.GetUnit("owl:equivalentProperty"), node, layers)
        if len(equivs) > 0:
            markup = ''
            for c in equivs:
                markup = markup + "<link property=\"owl:equivalentProperty\" href=\"%s\"/>\n" % c.id
            return markup
    return "<!-- no external mappings noted for this term. -->"

class ShowUnit (webapp2.RequestHandler):
    """ShowUnit exposes schema.org terms via Web RequestHandler
    (HTML/HTTP etc.).
    """

#    def __init__(self):
#        self.outputStrings = []

    def emitCacheHeaders(self):
        """Send cache-related headers via HTTP."""
        self.response.headers['Cache-Control'] = "public, max-age=43200" # 12h
        self.response.headers['Vary'] = "Accept, Accept-Encoding"

    def GetCachedText(self, node, layers='core'):
        """Return page text from node.id cache (if found, otherwise None)."""
        global PageCache
        cachekey = "%s:%s" % ( layers, node.id ) # was node.id
        if (cachekey in PageCache):
            return PageCache[cachekey]
        else:
            return None

    def AddCachedText(self, node, textStrings, layers='core'):
        """Cache text of our page for this node via its node.id.

        We can be passed a text string or an array of text strings.
        """
        global PageCache
        cachekey = "%s:%s" % ( layers, node.id ) # was node.id
        outputText = "".join(textStrings)
        log.debug("CACHING: %s" % node.id)
        PageCache[cachekey] = outputText
        return outputText

    def write(self, str):
        """Write some text to Web server's output stream."""
        self.outputStrings.append(str)


    def moreInfoBlock(self, node, layer='core'):

        # if we think we have more info on this term, show a bulleted list of extra items.

        # defaults
        bugs = ["No known open issues."]
        mappings = ["No recorded schema mappings."]
        items = bugs + mappings

        nodetype="Misc"

        if node.isEnumeration():
            nodetype = "enumeration"
        elif node.isDataType(layers=layer):
            nodetype = "datatype"
        elif node.isClass(layers=layer):
            nodetype = "type"
        elif node.isAttribute(layers=layer):
            nodetype = "property"
        elif node.isEnumerationValue(layers=layer):
            nodetype = "enumeratedvalue"

        feedback_url = FEEDBACK_FORM_BASE_URL.format("http://schema.org/{0}".format(node.id), nodetype)
        items = [

        "<a href='{0}'>Leave public feedback on this term &#128172;</a>".format(feedback_url),
        "<a href='https://github.com/schemaorg/schemaorg/issues?q=is%3Aissue+is%3Aopen+{0}'>Check for open issues.</a>".format(node.id)

        ]

        for l in all_terms[node.id]:
            l = l.replace("#","")
            if l == "core":
                ext = ""
            else:
                ext = "extension "
            if ENABLE_HOSTED_EXTENSIONS:
                items.append("'{0}' is mentioned in {1}layer: <a href='{2}'>{3}</a>".format( node.id, ext, makeUrl(l,node.id), l ))

        moreinfo = """<div>
        <div id='infobox' style='text-align: right;'><label role="checkbox" for=morecheck><b><span style="cursor: pointer;">[more...]</span></b></label></div>
        <input type='checkbox' checked="checked" style='display: none' id=morecheck><div id='infomsg' style='background-color: #EEEEEE; text-align: left; padding: 0.5em;'>
        <ul>"""

        for i in items:
            moreinfo += "<li>%s</li>" % i

#          <li>mappings to other terms.</li>
#          <li>or links to open issues.</li>

        moreinfo += "</ul>\n</div>\n</div>\n"
        return moreinfo

    def GetParentStack(self, node, layers='core'):
        """Returns a hiearchical structured used for site breadcrumbs."""
        thing = Unit.GetUnit("Thing")
        #log.info("GetParentStack for: %s",node)
        if (node not in self.parentStack):
            self.parentStack.append(node)

        if (Unit.isAttribute(node, layers=layers)):
            self.parentStack.append(Unit.GetUnit("Property"))
            self.parentStack.append(thing)

        sc = Unit.GetUnit("rdfs:subClassOf")
        if GetTargets(sc, node, layers=layers):
            for p in GetTargets(sc, node, layers=layers):
                self.GetParentStack(p, layers=layers)
        else:
            # Enumerations are classes that have no declared subclasses
            sc = Unit.GetUnit("rdf:type")
            for p in GetTargets(sc, node, layers=layers):
                self.GetParentStack(p, layers=layers)

#Put 'Thing' to the end for multiple inheritance classes
        if(thing in self.parentStack):
            self.parentStack.remove(thing)
            self.parentStack.append(thing)


    def ml(self, node, label='', title='', prop='', hashorslash='/'):
        """ml ('make link')
        Returns an HTML-formatted link to the class or property URL

        * label = optional anchor text label for the link
        * title = optional title attribute on the link
        * prop = an optional property value to apply to the A element
        """
        if(node.id == "DataType"):  #Special case
            return "<a href=\"%s\">%s</a>" % (node.id, node.id)

        if label=='':
          label = node.id
        if title != '':
          title = " title=\"%s\"" % (title)
        if prop:
            prop = " property=\"%s\"" % (prop)
        urlprefix = ""
        home = node.getHomeLayer()

        if home in ENABLED_EXTENSIONS and home != getHostExt():
            port = ""
            if getHostPort() != "80":
                port = ":%s" % getHostPort()
            urlprefix = makeUrl(home)

        extclass = ""
        extflag = ""
        tooltip = ""
        if home != "core" and home != "":
            extclass = "class=\"ext ext-%s\" " % home
            extflag = EXTENSION_SUFFIX
            tooltip = "title=\"Extended schema: %s.schema.org\" " % home

        rdfalink = ''
        if prop:
            rdfalink = '<link %s href="http://schema.org/%s" />' % (prop,label)


        return "%s<a %s %s href=\"%s%s%s\"%s>%s</a>%s" % (rdfalink,tooltip, extclass, urlprefix, hashorslash, node.id, title, label, extflag)
        #return "<a %s %s href=\"%s%s%s\"%s%s>%s</a>%s" % (tooltip, extclass, urlprefix, hashorslash, node.id, prop, title, label, extflag)

    def makeLinksFromArray(self, nodearray, tooltip=''):
        """Make a comma separate list of links via ml() function.

        * tooltip - optional text to use as title of all links
        """
        hyperlinks = []
        for f in nodearray:
           hyperlinks.append(self.ml(f, f.id, tooltip))
        return (", ".join(hyperlinks))

    def emitUnitHeaders(self, node, layers='core'):
        """Write out the HTML page headers for this node."""
        self.write("<h1 property=\"rdfs:label\" class=\"page-title\">\n")
        self.write(node.id)
        self.write("</h1>")
        home = node.home
        if home != "core" and home != "":
            self.write("Defined in the %s.schema.org extension.<br/>" % home)
            self.emitCanonicalURL(node)

        self.BreadCrumbs(node, layers=layers)

        comment = GetComment(node, layers)

        self.write(" <div property=\"rdfs:comment\">%s</div>\n\n" % (comment) + "\n")

        self.write(" <br/><div>Usage: %s</div>\n\n" % (node.UsageStr()) + "\n")

        #was:        self.write(self.moreInfoBlock(node))

        if (node.isClass(layers=layers) and not node.isDataType(layers=layers) and node.id != "DataType"):
            self.write("<table class=\"definition-table\">\n        <thead>\n  <tr><th>Property</th><th>Expected Type</th><th>Description</th>               \n  </tr>\n  </thead>\n\n")

    def emitCanonicalURL(self,node):
        cURL = "http://schema.org/" + node.id
        self.write(" <span class=\"canonicalUrl\">Canonical URL: <a href=\"%s\">%s</a></span>" % (cURL, cURL))

    # Stacks to support multiple inheritance
    crumbStacks = []
    def BreadCrumbs(self, node, layers):
        self.crumbStacks = []
        cstack = []
        self.crumbStacks.append(cstack)
        self.WalkCrumbs(node,cstack,layers=layers)
        if (node.isAttribute(layers=layers)):
            cstack.append(Unit.GetUnit("Property"))
            cstack.append(Unit.GetUnit("Thing"))

        enuma = node.isEnumerationValue(layers=layers)

        crumbsout = []
        for row in range(len(self.crumbStacks)):
           thisrow = ""
           if(":" in self.crumbStacks[row][len(self.crumbStacks[row])-1].id):
                continue
           count = 0
           while(len(self.crumbStacks[row]) > 0):
                n = self.crumbStacks[row].pop()
                if(count > 0):
                    if((len(self.crumbStacks[row]) == 0) and enuma):
                        thisrow += " :: "
                    else:
                        thisrow += " &gt; "
                elif n.id == "Class": # If Class is first breadcrum suppress it
                        continue
                count += 1
                thisrow += "%s" % (self.ml(n))
           crumbsout.append(thisrow)

        self.write("<h4>")
        rowcount = 0
        for crumb in sorted(crumbsout):
           if rowcount > 0:
               self.write("<br/>")
           self.write("<span class='breadcrumbs'>%s</span>\n" % crumb)
           rowcount += 1
        self.write("</h4>\n")

#Walk up the stack, appending crumbs & create new (duplicating crumbs already identified) if more than one parent found
    def WalkCrumbs(self, node, cstack, layers):
        if "http://" in node.id or "https://" in node.id:  #Suppress external class references
            return

        cstack.append(node)
        tmpStacks = []
        tmpStacks.append(cstack)
        subs = []

        if(node.isDataType(layers=layers)):
            subs = GetTargets(Unit.GetUnit("rdf:type"), node, layers=layers)
            subs += GetTargets(Unit.GetUnit("rdfs:subClassOf"), node, layers=layers)
        elif node.isClass(layers=layers):
            subs = GetTargets(Unit.GetUnit("rdfs:subClassOf"), node, layers=layers)
        elif(node.isAttribute(layers=layers)):
            subs = GetTargets(Unit.GetUnit("rdfs:subPropertyOf"), node, layers=layers)
        else:
            subs = GetTargets(Unit.GetUnit("rdf:type"), node, layers=layers)# Enumerations are classes that have no declared subclasses

        for i in range(len(subs)):
            if(i > 0):
                t = cstack[:]
                tmpStacks.append(t)
                self.crumbStacks.append(t)
        x = 0
        for p in subs:
            self.WalkCrumbs(p,tmpStacks[x],layers=layers)
            x += 1


    def emitSimplePropertiesPerType(self, cl, layers="core", out=None, hashorslash="/"):
        """Emits a simple list of properties applicable to the specified type."""

        if not out:
            out = self

        out.write("<ul class='props4type'>")
        for prop in sorted(GetSources(  Unit.GetUnit("domainIncludes"), cl, layers=layers), key=lambda u: u.id):
            if (prop.superseded(layers=layers)):
                continue
            out.write("<li><a href='%s%s'>%s</a></li>" % ( hashorslash, prop.id, prop.id  ))
        out.write("</ul>\n\n")

    def emitSimplePropertiesIntoType(self, cl, layers="core", out=None, hashorslash="/"):
        """Emits a simple list of properties whose values are the specified type."""

        if not out:
            out = self

        out.write("<ul class='props2type'>")
        for prop in sorted(GetSources(  Unit.GetUnit("rangeIncludes"), cl, layers=layers), key=lambda u: u.id):
            if (prop.superseded(layers=layers)):
                continue
            out.write("<li><a href='%s%s'>%s</a></li>" % ( hashorslash, prop.id, prop.id  ))
        out.write("</ul>\n\n")

    def ClassProperties (self, cl, subclass=False, layers="core", out=None, hashorslash="/"):
        """Write out a table of properties for a per-type page."""
        if not out:
            out = self

        propcount = 0

        headerPrinted = False
        di = Unit.GetUnit("domainIncludes")
        ri = Unit.GetUnit("rangeIncludes")

        for prop in sorted(GetSources(di, cl, layers=layers), key=lambda u: u.id):
            if (prop.superseded(layers=layers)):
                continue
            supersedes = prop.supersedes(layers=layers)
            olderprops = sorted(prop.supersedes_all(layers=layers),key=lambda u: u.id)
            inverseprop = prop.inverseproperty(layers=layers)
            subprops = sorted(prop.subproperties(layers=layers),key=lambda u: u.id)
            superprops = sorted(prop.superproperties(layers=layers),key=lambda u: u.id)
            ranges = sorted(GetTargets(ri, prop, layers=layers),key=lambda u: u.id)
            comment = GetComment(prop, layers=layers)
            if (not headerPrinted):
                class_head = self.ml(cl)
                if subclass:
                    class_head = self.ml(cl, prop="rdfs:subClassOf")
                out.write("<tr class=\"supertype\">\n     <th class=\"supertype-name\" colspan=\"3\">Properties from %s</th>\n  \n</tr>\n\n<tbody class=\"supertype\">\n  " % (class_head))
                headerPrinted = True

            out.write("<tr typeof=\"rdfs:Property\" resource=\"http://schema.org/%s\">\n    \n      <th class=\"prop-nam\" scope=\"row\">\n\n<code property=\"rdfs:label\">%s</code>\n    </th>\n " % (prop.id, self.ml(prop)))
            out.write("<td class=\"prop-ect\">\n")
            first_range = True
            for r in sorted(ranges,key=lambda u: u.id):
                if (not first_range):
                    out.write(" or <br/> ")
                first_range = False
                out.write(self.ml(r, prop='rangeIncludes'))
                out.write("&nbsp;")
            out.write("</td>")
            out.write("<td class=\"prop-desc\" property=\"rdfs:comment\">%s" % (comment))
            if (len(olderprops) > 0):
                olderlinks = ", ".join([self.ml(o) for o in olderprops])
                out.write(" Supersedes %s." % olderlinks )
            if (inverseprop != None):
                out.write("<br/> Inverse property: %s." % (self.ml(inverseprop)))

            out.write("</td></tr>")
            subclass = False
            propcount += 1

        if subclass: # in case the superclass has no defined attributes
            out.write("<tr><td colspan=\"3\"><meta property=\"rdfs:subClassOf\" content=\"%s\"></td></tr>" % (cl.id))

        return propcount

    def emitClassExtensionSuperclasses (self, cl, layers="core", out=None):
       first = True
       count = 0
       if not out:
           out = self

       buff = StringIO.StringIO()
       sc = Unit.GetUnit("rdfs:subClassOf")

       for p in GetTargets(sc, cl, ALL_LAYERS):

          if inLayer(layers,p):
               continue

          if p.id == "http://www.w3.org/2000/01/rdf-schema#Class": #Special case for "DataType"
              p.id = "Class"

          sep = ", "
          if first:
            sep = "<li>"
            first = False

          buff.write("%s%s" % (sep,self.ml(p)))
          count += 1

          if(count > 0):
            buff.write("</li>\n")

       content = buff.getvalue()
       if(len(content) > 0):
           if cl.id == "DataType":
               self.write("<h4>Subclass of:<h4>")
           else:
               self.write("<h4>Available supertypes defined in extensions</h4>")
           self.write("<ul>")
           self.write(content)
           self.write("</ul>")
       buff.close()

    def emitClassExtensionProperties (self, cl, layers="core", out=None):
       if not out:
           out = self

       buff = StringIO.StringIO()

       for p in self.parentStack:
           self._ClassExtensionProperties(buff, p, layers=layers)

       content = buff.getvalue()
       if(len(content) > 0):
           self.write("<h4>Available properties in extensions</h4>")
           self.write("<ul>")
           self.write(content)
           self.write("</ul>")
       buff.close()

    def _ClassExtensionProperties (self, out, cl, layers="core"):
        """Write out a list of properties not displayed as they are in extensions for a per-type page."""

        di = Unit.GetUnit("domainIncludes")

        
        exts = {}
        
        for prop in sorted(GetSources(di, cl, ALL_LAYERS), key=lambda u: u.id):
            if (prop.superseded(layers=layers)):
                continue
            if inLayer(layers,prop): #Already in the correct layer - no need to report
                continue
            if inLayer("meta",prop): #Suppress mentioning properties from the 'meta' extension.
                continue
            ext = prop.getHomeLayer()
            log.debug("ClassExtensionfFound %s from %s" % (prop, ext))
            if not ext in exts.keys():
                exts[ext] = []
            exts[ext].append(prop)
            
        for e in sorted(exts.keys()):
            log.info("%s EXTS %s: %s" % (cl, e,exts[e]))
            count = 0
            first = True
            for p in sorted(exts[e], key=lambda u: u.id):
                sep = ", "
                if first:
                    out.write("<li>For %s in the <a href=\"%s\">%s</a> extension:  " % (self.ml(cl),makeUrl(e,""),e))
                    sep = ""
                    first = False

                out.write("%s%s" % (sep,self.ml(p)))
                count += 1
            if(count > 0):
                out.write("</li>\n")


    def emitClassIncomingProperties (self, cl, layers="core", out=None, hashorslash="/"):
        """Write out a table of incoming properties for a per-type page."""
        if not out:
            out = self

        headerPrinted = False
        di = Unit.GetUnit("domainIncludes")
        ri = Unit.GetUnit("rangeIncludes")
#        log.info("Incomming for %s" % cl.id)
        for prop in sorted(GetSources(ri, cl, layers=layers), key=lambda u: u.id):
            if (prop.superseded(layers=layers)):
                continue
            supersedes = prop.supersedes(layers=layers)
            inverseprop = prop.inverseproperty(layers=layers)
            subprops = sorted(prop.subproperties(layers=layers),key=lambda u: u.id)
            superprops = sorted(prop.superproperties(layers=layers),key=lambda u: u.id)
            ranges = sorted(GetTargets(di, prop, layers=layers),key=lambda u: u.id)
            comment = GetComment(prop, layers=layers)

            if (not headerPrinted):
                self.write("<br/><br/>Instances of %s may appear as values for the following properties<br/>" % (self.ml(cl)))
                self.write("<table class=\"definition-table\">\n        \n  \n<thead>\n  <tr><th>Property</th><th>On Types</th><th>Description</th>               \n  </tr>\n</thead>\n\n")

                headerPrinted = True

            self.write("<tr>\n<th class=\"prop-nam\" scope=\"row\">\n <code>%s</code>\n</th>\n " % (self.ml(prop)) + "\n")
            self.write("<td class=\"prop-ect\">\n")
            first_range = True
            for r in ranges:
                if (not first_range):
                    self.write(" or<br/> ")
                first_range = False
                self.write(self.ml(r))
                self.write("&nbsp;")
            self.write("</td>")
            self.write("<td class=\"prop-desc\">%s " % (comment))
            if (supersedes != None):
                self.write(" Supersedes %s." % (self.ml(supersedes)))
            if (inverseprop != None):
                self.write("<br/> inverse property: %s." % (self.ml(inverseprop)) )

            self.write("</td></tr>")
        if (headerPrinted):
            self.write("</table>\n")


    def emitRangeTypesForProperty(self, node, layers="core", out=None, hashorslash="/"):
        """Write out simple HTML summary of this property's expected types."""
        if not out:
            out = self

        out.write("<ul class='attrrangesummary'>")
        for rt in sorted(GetTargets(Unit.GetUnit("rangeIncludes"), node, layers=layers), key=lambda u: u.id):
            out.write("<li><a href='%s%s'>%s</a></li>" % ( hashorslash, rt.id, rt.id  ))
        out.write("</ul>\n\n")


    def emitDomainTypesForProperty(self, node, layers="core", out=None, hashorslash="/"):
        """Write out simple HTML summary of types that expect this property."""
        if not out:
            out = self

        out.write("<ul class='attrdomainsummary'>")
        for dt in sorted(GetTargets(Unit.GetUnit("domainIncludes"), node, layers=layers), key=lambda u: u.id):
            out.write("<li><a href='%s%s'>%s</a></li>" % ( hashorslash, dt.id, dt.id  ))
        out.write("</ul>\n\n")



    def emitAttributeProperties(self, node, layers="core", out=None, hashorslash="/"):
        """Write out properties of this property, for a per-property page."""
        if not out:
            out = self

        di = Unit.GetUnit("domainIncludes")
        ri = Unit.GetUnit("rangeIncludes")
        ranges = sorted(GetTargets(ri, node, layers=layers), key=lambda u: u.id)
        domains = sorted(GetTargets(di, node, layers=layers), key=lambda u: u.id)
        first_range = True

        inverseprop = node.inverseproperty(layers=layers)
        subprops = sorted(node.subproperties(layers=layers),key=lambda u: u.id)
        superprops = sorted(node.superproperties(layers=layers),key=lambda u: u.id)


        if (inverseprop != None):
            tt = "This means the same thing, but with the relationship direction reversed."
            out.write("<p>Inverse-property: %s.</p>" % (self.ml(inverseprop, inverseprop.id,tt, prop=False, hashorslash=hashorslash)) )

        out.write("<table class=\"definition-table\">\n")
        out.write("<thead>\n  <tr>\n    <th>Values expected to be one of these types</th>\n  </tr>\n</thead>\n\n  <tr>\n    <td>\n      ")

        for r in ranges:
            if (not first_range):
                out.write("<br/>")
            first_range = False
            tt = "The '%s' property has values that include instances of the '%s' type." % (node.id, r.id)
            out.write(" <code>%s</code> " % (self.ml(r, r.id, tt, prop="rangeIncludes", hashorslash=hashorslash) +"\n"))
        out.write("    </td>\n  </tr>\n</table>\n\n")
        first_domain = True

        out.write("<table class=\"definition-table\">\n")
        out.write("  <thead>\n    <tr>\n      <th>Used on these types</th>\n    </tr>\n</thead>\n<tr>\n  <td>")
        for d in domains:
            if (not first_domain):
                out.write("<br/>")
            first_domain = False
            tt = "The '%s' property is used on the '%s' type." % (node.id, d.id)
            out.write("\n    <code>%s</code> " % (self.ml(d, d.id, tt, prop="domainIncludes",hashorslash=hashorslash)+"\n" ))
        out.write("      </td>\n    </tr>\n</table>\n\n")

        if (subprops != None and len(subprops) > 0):
            out.write("<table class=\"definition-table\">\n")
            out.write("  <thead>\n    <tr>\n      <th>Sub-properties</th>\n    </tr>\n</thead>\n")
            for sbp in subprops:
                c = ShortenOnSentence(StripHtmlTags( GetComment(sbp,layers=layers) ),60)
                tt = "%s: ''%s''" % ( sbp.id, c)
                out.write("\n    <tr><td><code>%s</code></td></tr>\n" % (self.ml(sbp, sbp.id, tt, hashorslash=hashorslash)))
            out.write("\n</table>\n\n")

        # Super-properties
        if (superprops != None and  len(superprops) > 0):
            out.write("<table class=\"definition-table\">\n")
            out.write("  <thead>\n    <tr>\n      <th>Super-properties</th>\n    </tr>\n</thead>\n")
            for spp in superprops:
                c = ShortenOnSentence(StripHtmlTags( GetComment(spp,layers=layers) ),60)
                c = re.sub(r'<[^>]*>', '', c) # This is not a sanitizer, we trust our input.
                tt = "%s: ''%s''" % ( spp.id, c)
                out.write("\n    <tr><td><code>%s</code></td></tr>\n" % (self.ml(spp, spp.id, tt,hashorslash)))
            out.write("\n</table>\n\n")

        self.emitSupersedes(node,layers=layers,out=out,hashorslash=hashorslash)

    def emitSupersedes(self, node, layers="core", out=None, hashorslash="/"):
        """Write out Supersedes and/or Superseded by for this term"""

        if not out:
            out = self
        newerprop = node.supersededBy(layers=layers) # None of one. e.g. we're on 'seller'(new) page, we get 'vendor'(old)
        olderprop = node.supersedes(layers=layers) # None or one
        olderprops = sorted(node.supersedes_all(layers=layers),key=lambda u: u.id) # list, e.g. 'seller' has 'vendor', 'merchant'.


        # Supersedes
        if (olderprops != None and len(olderprops) > 0):
            out.write("<table class=\"definition-table\">\n")
            out.write("  <thead>\n    <tr>\n      <th>Supersedes</th>\n    </tr>\n</thead>\n")

            for o in olderprops:
                c = GetComment(o, layers=layers)
                tt = "%s: ''%s''" % ( o.id, c)
                out.write("\n    <tr><td><code>%s</code></td></tr>\n" % (self.ml(o, o.id, tt, hashorslash)))
            out.write("\n</table>\n\n")

        # supersededBy (at most one direct successor)
        if (newerprop != None):
            out.write("<table class=\"definition-table\">\n")
            out.write("  <thead>\n    <tr>\n      <th><a href=\"/supersededBy\">supersededBy</a></th>\n    </tr>\n</thead>\n")
            tt="supersededBy: %s" % newerprop.id
            out.write("\n    <tr><td><code>%s</code></td></tr>\n" % (self.ml(newerprop, newerprop.id, tt,hashorslash)))
            out.write("\n</table>\n\n")

    def rep(self, markup):
        """Replace < and > with HTML escape chars."""
        m1 = re.sub("<", "&lt;", markup)
        m2 = re.sub(">", "&gt;", m1)
        # TODO: Ampersand? Check usage with examples.
        return m2

    def handleHomepage(self, node):
        """Send the homepage, or if no HTML accept header received and JSON-LD was requested, send JSON-LD context file.

        typical browser accept list: ('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8')
        # e.g. curl -H "Accept: application/ld+json" http://localhost:8080/
        see also http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
        https://github.com/rvguha/schemaorg/issues/5
        https://github.com/rvguha/schemaorg/wiki/JsonLd
        """
        accept_header = self.request.headers.get('Accept').split(',')
        log.info("Home page - accepts: %s" % self.request.headers.get('Accept'))

        # Homepage is content-negotiated. HTML or JSON-LD.
        mimereq = {}
        for ah in accept_header:
            ah = re.sub( r";q=\d?\.\d+", '', ah).rstrip()
            mimereq[ah] = 1

        html_score = mimereq.get('text/html', 5)
        xhtml_score = mimereq.get('application/xhtml+xml', 5)
        jsonld_score = mimereq.get('application/ld+json', 10)
        # print "accept_header: " + str(accept_header) + " mimereq: "+str(mimereq) + "Scores H:{0} XH:{1} J:{2} ".format(html_score,xhtml_score,jsonld_score)

        if (ENABLE_JSONLD_CONTEXT and (jsonld_score < html_score and jsonld_score < xhtml_score)):
            jsonldcontext = GetJsonLdContext(layers=ALL_LAYERS)
            self.response.headers['Content-Type'] = "application/ld+json"
            self.emitCacheHeaders()
            self.response.out.write( jsonldcontext )
            return True
        else:
            # Serve a homepage from template
            # the .tpl has responsibility for extension homepages
            # TODO: pass in extension, base_domain etc.
            sitekeyedhomepage = "homepage %s" % getSiteName()
            hp = DataCache.get(sitekeyedhomepage)
            self.emitCacheHeaders()
            if hp != None:
                self.response.out.write( hp )
                #log.info("Served datacache homepage.tpl key: %s" % sitekeyedhomepage)
                log.debug("Served datacache homepage.tpl key: %s" % sitekeyedhomepage)
            else:


                template_values = {
                    'ext_contents': self.handleExtensionContents(getHostExt()),
                    'home_page': "True",
                }
                page = templateRender('homepage.tpl',template_values)
                self.response.out.write( page )
                log.debug("Served and cached fresh homepage.tpl key: %s " % sitekeyedhomepage)
                #log.info("Served and cached fresh homepage.tpl key: %s " % sitekeyedhomepage)
                DataCache.put(sitekeyedhomepage, page)
                #            self.response.out.write( open("static/index.html", 'r').read() )
            return True
        log.info("Warning: got here how?")
        return False

    def getExtendedSiteName(self, layers):
        """Returns site name (domain name), informed by the list of active layers."""
        if layers==["core"]:
            return "schema.org"
        if len(layers)==0:
            return "schema.org"
        return (getHostExt() + ".schema.org")

    def emitSchemaorgHeaders(self, node, ext_mappings='', sitemode="default", sitename="schema.org", layers="core"):
        """
        Generates, caches and emits HTML headers for class, property and enumeration pages. Leaves <body> open.

        * entry = name of the class or property
        """
        rdfs_type = 'rdfs:Property'
        anode = True
        if isinstance(node, str):
            entry = node
            anode = False
        else:
            entry = node.id

            if node.isEnumeration():
                rdfs_type = 'rdfs:Class'
            elif node.isEnumerationValue():
                rdfs_type = ""
                nodeTypes = GetTargets(Unit.GetUnit("rdf:type"), node, layers=layers)
                typecount = 0
                for type in nodeTypes:
                     if typecount > 0:
                         rdfs_type += " "
                     rdfs_type += type.id
                     typecount += 1

            elif node.isClass():
                rdfs_type = 'rdfs:Class'
            elif node.isAttribute():
                rdfs_type = 'rdfs:Property'

        generated_page_id = "genericTermPageHeader-%s-%s" % ( str(entry), getSiteName() )
        gtp = DataCache.get( generated_page_id )

        if gtp != None:
            self.response.out.write( gtp )
            log.debug("Served recycled genericTermPageHeader.tpl for %s" % generated_page_id )
        else:

            desc = entry
            if anode:
                desc = self.getMetaDescription(node, layers=layers, lengthHint=200)

            template_values = {
                'entry': str(entry),
                'desc' : desc,
                'menu_sel': "Schemas",
                'rdfs_type': rdfs_type,
                'ext_mappings': ext_mappings
            }
            out = templateRender('genericTermPageHeader.tpl',template_values)
            DataCache.put(generated_page_id,out)
            log.debug("Served and cached fresh genericTermPageHeader.tpl for %s" % generated_page_id )

            self.response.write(out)

    def getMetaDescription(self, node, layers="core",lengthHint=250):
        ins = ""
        if node.isEnumeration():
            ins += " Enumeration Type"
        elif node.isClass():
            ins += " Type"
        elif node.isAttribute():
            ins += " Property"
        elif node.isEnumerationValue():
            ins += " Enumeration Value"

        desc = "Schema.org%s: %s - " % (ins, node.id)

        lengthHint -= len(desc)

        comment = GetComment(node, layers)

        desc += ShortenOnSentence(StripHtmlTags(comment),lengthHint)

        return desc




    def emitExactTermPage(self, node, layers="core"):
        """Emit a Web page that exactly matches this node."""
        log.debug("EXACT PAGE: %s" % node.id)
        self.outputStrings = [] # blank slate
        ext_mappings = GetExtMappingsRDFa(node, layers=layers)

        global sitemode #,sitename
        if ("schema.org" not in self.request.host and sitemode == "mainsite"):
            sitemode = "mainsite testsite"

        self.emitSchemaorgHeaders(node, ext_mappings, sitemode, getSiteName(), layers)


        cached = self.GetCachedText(node, layers)
        if (cached != None):
            self.response.write(cached)
            return

        self.parentStack = []
        self.GetParentStack(node, layers=layers)

        self.emitUnitHeaders(node,  layers=layers) # writes <h1><table>...

        if (node.isEnumerationValue(layers=layers)):
            self.write(self.moreInfoBlock(node))

        if (node.isClass(layers=layers)):
            subclass = True
            self.write(self.moreInfoBlock(node))

            for p in self.parentStack:
                self.ClassProperties(p, p==self.parentStack[0], layers=layers)
            if (not node.isDataType(layers=layers) and node.id != "DataType"):
                self.write("\n\n</table>\n\n")
            self.emitClassIncomingProperties(node, layers=layers)

            self.emitClassExtensionSuperclasses(node,layers)

            self.emitClassExtensionProperties(p,layers)

            self.emitSupersedes(node,layers=layers)


        elif (Unit.isAttribute(node, layers=layers)):
            self.write(self.moreInfoBlock(node))
            self.emitAttributeProperties(node, layers=layers)

        if (node.isClass(layers=layers)):
            children = []
            children = GetSources(Unit.GetUnit("rdfs:subClassOf"), node, ALL_LAYERS)# Normal subclasses
            if(node.isDataType() or node.id == "DataType"):
                children += GetSources(Unit.GetUnit("rdf:type"), node, ALL_LAYERS)# Datatypes
            children = sorted(children, key=lambda u: u.id)

            if (len(children) > 0):
                buff = StringIO.StringIO()
                extbuff = StringIO.StringIO()

                firstext=True
                for c in children:
                    if c.superseded(layers=layers):
                        continue
                    if inLayer(layers, c):
                        buff.write("<li> %s </li>" % (self.ml(c)))
                    else:
                        sep = ", "
                        if firstext:
                            sep = ""
                            firstext=False
                        extbuff.write("%s%s" % (sep,self.ml(c)) )

                if (len(buff.getvalue()) > 0):
                    if node.isDataType():
                        self.write("<br/><b>More specific DataTypes</b><ul>")
                    else:
                        self.write("<br/><b>More specific Types</b><ul>")
                    self.write(buff.getvalue())
                    self.write("</ul>")

                if (len(extbuff.getvalue()) > 0):
                    self.write("<h4>More specific Types available in extensions</h4><ul><li>")
                    self.write(extbuff.getvalue())
                    self.write("</li></ul>")
                buff.close()
                extbuff.close()

        if (node.isEnumeration(layers=layers)):

            children = sorted(GetSources(Unit.GetUnit("rdf:type"), node, ALL_LAYERS), key=lambda u: u.id)
            if (len(children) > 0):
                buff = StringIO.StringIO()
                extbuff = StringIO.StringIO()

                firstext=True
                for c in children:
                    if inLayer(layers, c):
                        buff.write("<li> %s </li>" % (self.ml(c)))
                    else:
                        sep = ","
                        if firstext:
                            sep = ""
                            firstext=False
                        extbuff.write("%s%s" % (sep,self.ml(c)) )

                if (len(buff.getvalue()) > 0):
                    self.write("<br/><br/><b>Enumeration members</b><ul>")
                    self.write(buff.getvalue())
                    self.write("</ul>")

                if (len(extbuff.getvalue()) > 0):
                    self.write("<h4>Enumeration members available in extensions</h4><ul><li>")
                    self.write(extbuff.getvalue())
                    self.write("</li></ul>")
                buff.close()
                extbuff.close()

        ackorgs = GetTargets(Unit.GetUnit("dc:source"), node, layers=layers)
        if (len(ackorgs) > 0):
            sources = []
            acknowledgements =[]
            for ao in ackorgs:
                acks = sorted(GetTargets(Unit.GetUnit("rdfs:comment"), ao, layers))
                if len(acks) == 0:
                    val = str(ao)
                    if val.startswith("http://") or val.startswith("https://"):
                        val = "[%s](%s)" % (val,val) #Put into markdown format
                    sources.append(val)
                else:
                    for ack in acks:
                        acknowledgements.append(ack)

            if len(sources) > 0:
                s = ""
                if len(sources) > 1:
                    s = "s"
                self.write("<h4  id=\"acks\">Source%s</h4>\n" % s)
                for so in sorted(sources):
                    self.write(MD.parse(so,True))
            if len(acknowledgements) > 0:
                s = ""
                if len(acknowledgements) > 1:
                    s = "s"
                self.write("<h4  id=\"acks\">Acknowledgement%s</h4>\n" % s)
                for ack in sorted(acknowledgements):
                    self.write(MD.parse(str(ack),True))

        examples = GetExamples(node, layers=layers)
        log.debug("Rendering n=%s examples" % len(examples))
        if (len(examples) > 0):
            example_labels = [
              ('Without Markup', 'original_html', 'selected'),
              ('Microdata', 'microdata', ''),
              ('RDFa', 'rdfa', ''),
              ('JSON-LD', 'jsonld', ''),
            ]
            self.write("<br/><br/><b><a id=\"examples\">Examples</a></b><br/><br/>\n\n")
            exNum = 0
            for ex in sorted(examples, key=lambda u: u.orderId):
                if not ex.egmeta["layer"] in layers: #Example defined in extension we are not in
                    continue
                exNum += 1
                id="example-%s" % exNum
                if "id" in ex.egmeta:
                    id = ex.egmeta["id"]
                self.write("<div title=\"%s\"><a id=\"%s\">Example %s</a></div>" % (id,id,exNum))
                self.write("<div class='ds-selector-tabs ds-selector'>\n")
                self.write("  <div class='selectors'>\n")
                for label, example_type, selected in example_labels:
                    self.write("    <a data-selects='%s' class='%s'>%s</a>\n"
                               % (example_type, selected, label))
                self.write("</div>\n\n")
                for label, example_type, selected in example_labels:
                    self.write("<pre class=\"prettyprint lang-html linenums %s %s\">%s</pre>\n\n"
                               % (example_type, selected, self.rep(ex.get(example_type))))
                self.write("</div>\n\n")

        self.write("<p class=\"version\"><b>Schema Version %s</b></p>\n\n" % SCHEMA_VERSION)
        # TODO: add some version info regarding the extension

        # Analytics
	self.write("""<script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
	  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
	  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
	  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
	  ga('create', 'UA-52672119-1', 'auto');ga('send', 'pageview');</script>""")

        self.write(" \n\n</div>\n</body>\n</html>")

        self.response.write(self.AddCachedText(node, self.outputStrings, layers))

    def emitHTTPHeaders(self, node):
        if ENABLE_CORS:
            self.response.headers.add_header("Access-Control-Allow-Origin", "*") # entire site is public.
            # see http://en.wikipedia.org/wiki/Cross-origin_resource_sharing

    def setupExtensionLayerlist(self, node):
        # Identify which extension layer(s) are requested
        # TODO: add subdomain support e.g. bib.schema.org/Globe
        # instead of Globe?ext=bib which is more for debugging.

        # 1. get a comma list from ?ext=foo,bar URL notation
        extlist = cleanPath( self.request.get("ext")  )# for debugging
        extlist = re.sub(ext_re, '', extlist).split(',')
        log.debug("?ext= extension list: %s " % ", ".join(extlist))

        # 2. Ignore ?ext=, start with 'core' only.
        layerlist = [ "core"]

        # 3. Use host_ext if set, e.g. 'bib' from bib.schema.org
        if getHostExt() != None:
            log.debug("Host: %s host_ext: %s" % ( self.request.host , getHostExt() ) )
            extlist.append(getHostExt())

        # Report domain-requested extensions
        for x in extlist:
            log.debug("Ext filter found: %s" % str(x))
            if x  in ["core", "localhost", ""]:
                continue
            layerlist.append("%s" % str(x))
        layerlist = list(set(layerlist))   # dedup
        log.debug("layerlist: %s" % layerlist)
        return layerlist

    def handleJSONContext(self, node):
        """Handle JSON-LD Context non-homepage requests (including refuse if not enabled)."""

        if not ENABLE_JSONLD_CONTEXT:
            self.error(404)
            self.response.out.write('<title>404 Not Found.</title><a href="/">404 Not Found (JSON-LD Context not enabled.)</a><br/><br/>')
            return True

        if (node=="docs/jsonldcontext.json.txt"):
            jsonldcontext = GetJsonLdContext(layers=ALL_LAYERS)
            self.response.headers['Content-Type'] = "text/plain"
            self.emitCacheHeaders()
            self.response.out.write( jsonldcontext )
            return True
        if (node=="docs/jsonldcontext.json"):
            jsonldcontext = GetJsonLdContext(layers=ALL_LAYERS)
            self.response.headers['Content-Type'] = "application/ld+json"
            self.emitCacheHeaders()
            self.response.out.write( jsonldcontext )
            return True
        return False
        # see also handleHomepage for conneg'd version.

    def handleSchemasPage(self, node,  layerlist='core'):
        self.response.headers['Content-Type'] = "text/html"
        self.emitCacheHeaders()

        if DataCache.get('SchemasPage'):
            self.response.out.write( DataCache.get('SchemasPage') )
            log.debug("Serving recycled SchemasPage.")
            return True
        else:
            extensions = []
            for ex in sorted(ENABLED_EXTENSIONS):
                extensions.append("<a href=\"%s\">%s.schema.org</a>" % (makeUrl(ex,""),ex))

            page = templateRender('schemas.tpl',{'counts': self.getCounts(),
                                    'extensions': extensions,
                                    'menu_sel': "Schemas"})

            self.response.out.write( page )
            log.debug("Serving fresh SchemasPage.")
            DataCache.put("SchemasPage",page)

            return True

    def getCounts(self):
        text = ""
        text += "The core vocabulary currently consists of %s Types, " % len(GetAllTypes("core"))
        text += " %s Properties, " % len(GetAllProperties("core"))
        text += "and %s Enumeration values." % len(GetAllEnumerationValues("core"))
        return text


    def handleFullHierarchyPage(self, node,  layerlist='core'):
        self.response.headers['Content-Type'] = "text/html"
        self.emitCacheHeaders()

        if DataCache.get('FullTreePage'):
            self.response.out.write( DataCache.get('FullTreePage') )
            log.debug("Serving recycled FullTreePage.")
            return True
        else:
            template = JINJA_ENVIRONMENT.get_template('full.tpl')


            extlist=""
            extonlylist=[]
            count=0
            for i in layerlist:
                if i != "core":
                    sep = ""
                    if count > 0:
                        sep = ", "
                    extlist += "'%s'%s" % (i, sep)
                    extonlylist.append(i)
                    count += 1
            local_button = ""
            local_label = "<h3>Core plus %s extension vocabularies</h3>" % extlist
            if count == 0:
                local_button = "Core vocabulary"
            elif count == 1:
                local_button = "Core plus %s extension" % extlist
            else:
                local_button = "Core plus %s extensions" % extlist

            ext_button = ""
            if count == 1:
                ext_button = "Extension %s" % extlist
            elif count > 1:
                ext_button = "Extensions %s" % extlist


            uThing = Unit.GetUnit("Thing")
            uDataType = Unit.GetUnit("DataType")

            mainroot = TypeHierarchyTree(local_label)
            mainroot.traverseForHTML(uThing, layers=layerlist)
            thing_tree = mainroot.toHTML()
            #az_enums = GetAllEnumerationValues(layerlist)
            #az_enums.sort( key = lambda u: u.id)
            #thing_tree += self.listTerms(az_enums,"<br/><strong>Enumeration Values</strong><br/>")


            fullmainroot = TypeHierarchyTree("<h3>Core plus all extension vocabularies</h3>")
            fullmainroot.traverseForHTML(uThing, layers=ALL_LAYERS)
            full_thing_tree = fullmainroot.toHTML()
            #az_enums = GetAllEnumerationValues(ALL_LAYERS)
            #az_enums.sort( key = lambda u: u.id)
            #full_thing_tree += self.listTerms(az_enums,"<br/><strong>Enumeration Values</strong><br/>")

            ext_thing_tree = None
            if len(extonlylist) > 0:
                extroot = TypeHierarchyTree("<h3>Extension: %s</h3>" % extlist)
                extroot.traverseForHTML(uThing, layers=extonlylist)
                ext_thing_tree = extroot.toHTML()
                #az_enums = GetAllEnumerationValues(extonlylist)
                #az_enums.sort( key = lambda u: u.id)
                #ext_thing_tree += self.listTerms(az_enums,"<br/><strong>Enumeration Values</strong><br/>")


            dtroot = TypeHierarchyTree("<h4>Data Types</h4>")
            dtroot.traverseForHTML(uDataType, layers=layerlist)
            datatype_tree = dtroot.toHTML()

            full_button = "Core plus all extensions"

            page = templateRender('full.tpl',{ 'thing_tree': thing_tree,
                                    'full_thing_tree': full_thing_tree,
                                    'ext_thing_tree': ext_thing_tree,
                                    'datatype_tree': datatype_tree,
                                    'local_button': local_button,
                                    'full_button': full_button,
                                    'ext_button': ext_button,
                                    'menu_sel': "Schemas"})

            self.response.out.write( page )
            log.debug("Serving fresh FullTreePage.")
            DataCache.put("FullTreePage",page)

            return True

    def handleJSONSchemaTree(self, node, layerlist='core'):
        """Handle a request for a JSON-LD tree representation of the schemas (RDFS-based)."""

        self.response.headers['Content-Type'] = "application/ld+json"
        self.emitCacheHeaders()

        if DataCache.get('JSONLDThingTree'):
            self.response.out.write( DataCache.get('JSONLDThingTree') )
            log.debug("Serving recycled JSONLDThingTree.")
            return True
        else:
            uThing = Unit.GetUnit("Thing")
            mainroot = TypeHierarchyTree()
            mainroot.traverseForJSONLD(Unit.GetUnit("Thing"), layers=layerlist)
            thing_tree = mainroot.toJSON()
            self.response.out.write( thing_tree )
            log.debug("Serving fresh JSONLDThingTree.")
            DataCache.put("JSONLDThingTree",thing_tree)
            return True
        return False


    def handleExactTermPage(self, node, layers='core'):
        """Handle with requests for specific terms like /Person, /fooBar. """

        self.emitCacheHeaders()
        schema_node = Unit.GetUnit(node) # e.g. "Person", "CreativeWork".
        #log.info("Node in layer: %s" % inLayer(layers, schema_node))
        if inLayer(layers, schema_node):
            self.emitExactTermPage(schema_node, layers=layers)
            return True
        else:
            # log.info("Looking for node: %s in layers: %s" % (node.id, ",".join(all_layers.keys() )) )
            if not ENABLE_HOSTED_EXTENSIONS:
                return False
            if schema_node is not None and schema_node.id in all_terms:# look for it in other layers
                log.debug("TODO: layer toc: %s" % all_terms[schema_node.id] )
                # self.response.out.write("Layers should be listed here. %s " %  all_terms[node.id] )

                extensions = []
                for x in all_terms[schema_node.id]:
                    x = x.replace("#","")
                    ext = {}
                    ext['href'] = makeUrl(x,schema_node.id)
                    ext['text'] = x
                    extensions.append(ext)
                    #self.response.out.write("<li><a href='%s'>%s</a></li>" % (makeUrl(x,schema_node.id), x) )

                template = JINJA_ENVIRONMENT.get_template('wrongExt.tpl')
                page = templateRender('wrongExt.tpl', 
                                        {'target': schema_node.id,
                                        'extensions': extensions,
                                        'sitename': "schema.org"})

                self.response.out.write( page )
                log.debug("Serving fresh wrongExtPage.")
                return True
            return False

    def handle404Failure(self, node, layers="core"):
        self.error(404)
        self.emitSchemaorgHeaders("404%20Missing")
        self.response.out.write('<h3>404 Not Found.</h3><p><br/>Page not found. Please <a href="/">try the homepage.</a><br/><br/></p>')


        clean_node = cleanPath(node)

        log.debug("404: clean_node: clean_node: %s node: %s" % (clean_node, node))

        base_term = Unit.GetUnit( node.rsplit('/')[0] )
        if base_term != None :
            self.response.out.write('<div>Perhaps you meant: <a href="/%s">%s</a></div> <br/><br/> ' % ( base_term.id, base_term.id ))

        base_actionprop = Unit.GetUnit( node.rsplit('-')[0] )
        if base_actionprop != None :
            self.response.out.write('<div>Looking for an <a href="/Action">Action</a>-related property? Note that xyz-input and xyz-output have <a href="/docs/actions.html">special meaning</a>. See also: <a href="/%s">%s</a></div> <br/><br/> ' % ( base_actionprop.id, base_actionprop.id ))

        self.response.out.write("</div>\n</body>\n</html>\n")

        return True

    def handleFullReleasePage(self, node,  layerlist='core'):

        """Deal with a request for a full release summary page. Lists all terms and their descriptions inline in one long page.
        version/latest/ is from current schemas, others will need to be loaded and emitted from stored HTML snapshots (for now)."""

        # http://jinja.pocoo.org/docs/dev/templates/

        global releaselog
        clean_node = cleanPath(node)
        self.response.headers['Content-Type'] = "text/html"
        self.emitCacheHeaders()

        requested_version = clean_node.rsplit('/')[1]
        requested_format = clean_node.rsplit('/')[-1]
        if len( clean_node.rsplit('/') ) == 2:
            requested_format=""

        log.info("Full release page for: node: '%s' cleannode: '%s' requested_version: '%s' requested_format: '%s' l: %s" % (node, clean_node, requested_version, requested_format, len(clean_node.rsplit('/')) ) )

        # Full release page for: node: 'version/' cleannode: 'version/' requested_version: '' requested_format: '' l: 2
        # /version/
        log.debug("clean_node: %s requested_version: %s " %  (clean_node, requested_version))
        if (clean_node=="version/" or clean_node=="version") and requested_version=="" and requested_format=="":
            log.info("Table of contents should be sent instead, then succeed.")
            if DataCache.get('tocVersionPage'):
                self.response.out.write( DataCache.get('tocVersionPage'))
                return True
            else:
                log.debug("Serving tocversionPage from cache.")
                page = templateRender('tocVersionPage.tpl',
                        {"releases": releaselog.keys(),
                         "menu_sel": "Schemas"})

                self.response.out.write( page )
                log.debug("Serving fresh tocVersionPage.")
                DataCache.put("tocVersionPage",page)
                return True

        if requested_version in releaselog:
            log.info("Version '%s' was released on %s. Serving from filesystem." % ( node, releaselog[requested_version] ))

            version_rdfa = "data/releases/%s/schema.rdfa" % requested_version
            version_allhtml = "data/releases/%s/schema-all.html" % requested_version
            version_nt = "data/releases/%s/schema.nt" % requested_version

            if requested_format=="":
                self.response.out.write( open(version_allhtml, 'r').read() )
                return True
                # log.info("Skipping filesystem for now.")

            if requested_format=="schema.rdfa":
                self.response.headers['Content-Type'] = "application/octet-stream" # It is HTML but ... not really.
                self.response.headers['Content-Disposition']= "attachment; filename=schemaorg_%s.rdfa.html" % requested_version
                self.response.out.write( open(version_rdfa, 'r').read() )
                return True

            if requested_format=="schema.nt":
                self.response.headers['Content-Type'] = "application/n-triples" # It is HTML but ... not really.
                self.response.headers['Content-Disposition']= "attachment; filename=schemaorg_%s.rdfa.nt" % requested_version
                self.response.out.write( open(version_nt, 'r').read() )
                return True

            if requested_format != "":
                return False # Turtle, csv etc.

        else:
            log.info("Unreleased version requested. We only understand requests for latest if unreleased.")

            if requested_version != "latest":
                return False
                log.info("giving up to 404.")
            else:
                log.info("generating a live view of this latest release.")


        if DataCache.get('FullReleasePage'):
            self.response.out.write( DataCache.get('FullReleasePage') )
            log.debug("Serving recycled FullReleasePage.")
            return True
        else:
            mainroot = TypeHierarchyTree()
            mainroot.traverseForHTML(Unit.GetUnit("Thing"), hashorslash="#term_", layers=layerlist)
            thing_tree = mainroot.toHTML()
            base_href = "/version/%s/" % requested_version

            az_types = GetAllTypes()
            az_types.sort( key=lambda u: u.id)
            az_type_meta = {}

            az_props = GetAllProperties()
            az_props.sort( key = lambda u: u.id)
            az_prop_meta = {}


#TODO: ClassProperties (self, cl, subclass=False, layers="core", out=None, hashorslash="/"):

            # TYPES
            for t in az_types:
                props4type = HTMLOutput() # properties applicable for a type
                props2type = HTMLOutput() # properties that go into a type

                self.emitSimplePropertiesPerType(t, out=props4type, hashorslash="#term_" )
                self.emitSimplePropertiesIntoType(t, out=props2type, hashorslash="#term_" )

                #self.ClassProperties(t, out=typeInfo, hashorslash="#term_" )
                tcmt = Markup(GetComment(t))
                az_type_meta[t]={}
                az_type_meta[t]['comment'] = tcmt
                az_type_meta[t]['props4type'] = props4type.toHTML()
                az_type_meta[t]['props2type'] = props2type.toHTML()

            # PROPERTIES
            for pt in az_props:
                attrInfo = HTMLOutput()
                rangeList = HTMLOutput()
                domainList = HTMLOutput()
                # self.emitAttributeProperties(pt, out=attrInfo, hashorslash="#term_" )
                # self.emitSimpleAttributeProperties(pt, out=rangedomainInfo, hashorslash="#term_" )

                self.emitRangeTypesForProperty(pt, out=rangeList, hashorslash="#term_" )
                self.emitDomainTypesForProperty(pt, out=domainList, hashorslash="#term_" )

                cmt = Markup(GetComment(pt))
                az_prop_meta[pt] = {}
                az_prop_meta[pt]['comment'] = cmt
                az_prop_meta[pt]['attrinfo'] = attrInfo.toHTML()
                az_prop_meta[pt]['rangelist'] = rangeList.toHTML()
                az_prop_meta[pt]['domainlist'] = domainList.toHTML()

            page = templateRender('fullReleasePage.tpl',
                    {"base_href": base_href, 
                    'thing_tree': thing_tree,
                    'liveversion': SCHEMA_VERSION,
                    'requested_version': requested_version,
                    'releasedate': releaselog[str(SCHEMA_VERSION)],
                    'az_props': az_props, 'az_types': az_types,
                    'az_prop_meta': az_prop_meta, 'az_type_meta': az_type_meta,
                    'menu_sel': "Documentation"})

            self.response.out.write( page )
            log.debug("Serving fresh FullReleasePage.")
            DataCache.put("FullReleasePage",page)
            return True

    def handleExtensionContents(self,ext):
        if not ext in ENABLED_EXTENSIONS:
            return ""

        if DataCache.get('ExtensionContents',ext):
            return DataCache.get('ExtensionContents',ext)

        buff = StringIO.StringIO()

        az_terms = GetAllTerms(ext) #Returns sorted by id results.
        az_terms.sort(key = lambda u: u.category)

        if len(az_terms) > 0:
            buff.write("<br/><div style=\"text-align: left; margin: 2em\"><h3>Terms defined or referenced in the '%s' extension.</h3>" % ext)

            keys = []
            groups = []
            for k,g in itertools.groupby(az_terms, key = lambda u: u.category):
                keys.append(k)
                groups.append(list(g))

            i = 0
            while i < len(groups):
                groups[i] = sorted(groups[i],key = lambda u: u.id)
                i += 1

            g=0
            while g < len(groups):
                if g > 0:
                    buff.write("<br/>")
                buff.write(self.listTerms(groups[g],"<br/>%s Types (%s)<br/>" %
                                         (keys[g],self.countTypes(groups[g],select="type",layers=ext)),select="type",layers=ext))
                buff.write(self.listTerms(groups[g],"<br/>%s Properties (%s)<br/>" %
                                         (keys[g],self.countTypes(groups[g],select="prop",layers=ext)),select="prop",layers=ext))
                buff.write(self.listTerms(groups[g],"<br/>%s Enumeration values (%s)<br/>" %
                                         (keys[g],self.countTypes(groups[g],select="enum",layers=ext)),select="enum",layers=ext))
                g += 1
            buff.write("</div>")

        ret = buff.getvalue()
        DataCache.put('ExtensionContents',ret,ext)
        buff.close()
        return ret

    def countTypes(self,interms,select="",layers='core'):
        ret = 0
        for t in interms:
            if select == "type" and t.isClass(layers):
                ret += 1
            elif select == "prop" and t.isAttribute(layers):
                ret += 1
            elif select == "enum" and t.isEnumerationValue(layers):
                ret +=1
            elif select == "":
                ret += 1
        return ret


    def listTerms(self,interms,prefix="",select=None,layers='core'):
        buff = StringIO.StringIO()
        terms = interms
        if select:
            terms = []
            for t in interms:
                use = False
                if select == "type":
                    use = t.isClass(layers)
                elif select == "prop":
                    use = t.isAttribute(layers)
                elif select == "enum":
                    use = t.isEnumerationValue(layers)
                if use:
                    terms.append(t)

        if(len(terms) > 0):
            buff.write(prefix)
            first = True
            sep = ""
            for term in terms:
                if not first:
                    sep = ", "
                else:
                    first = False
                buff.write("%s%s" % (sep,self.ml(term)))

        ret = buff.getvalue()
        buff.close()
        return ret


    def setupHostinfo(self, node, test=""):
        hostString = test
        if test == "":
            hostString = self.request.host

        scheme = "http" #Defalt for tests
        if not getInTestHarness():  #Get the actual scheme from the request
            scheme = self.request.scheme

        host_ext = re.match( r'([\w\-_]+)[\.:]?', hostString).group(1)
        log.info("setupHostinfo: scheme=%s hoststring=%s host_ext?=%s" % (scheme, hostString, str(host_ext) ))

        setHttpScheme(scheme)

        split = hostString.rsplit(':')
        myhost = split[0]
        mybasehost = myhost
        myport = "80"
        if len(split) > 1:
            myport = split[1]

        setHostExt(host_ext)
        setBaseHost(mybasehost)
        setHostPort(myport)

        if host_ext != None:
            # e.g. "bib"
            log.debug("HOST: Found %s in %s" % ( host_ext, hostString ))
            if host_ext == "www":
                # www is special case that cannot be an extension - need to redirect to basehost
                mybasehost = mybasehost[4:]
                setBaseHost(mybasehost)
                return self.redirectToBase(node)
            elif not host_ext in ENABLED_EXTENSIONS:
                host_ext = ""
            else:
                mybasehost = mybasehost[len(host_ext) + 1:]
            setHostExt(host_ext)
            setBaseHost(mybasehost)

        dcn = host_ext
        if dcn == None or dcn == "" or dcn =="core":
            dcn = "core"
        if scheme != "http":
            dcn = "%s-%s" % (dcn,scheme)

        log.debug("sdoapp.py setting current datacache to: %s " % dcn)
        DataCache.setCurrent(dcn)


        debugging = False
        if "localhost" in hostString or "sdo-phobos.appspot.com" in hostString or FORCEDEBUGGING:
            debugging = True
        setAppVar('debugging',debugging)

        return True

    def redirectToBase(self,node=""):
        uri = makeUrl("",node)
        self.response = webapp2.redirect(uri, True, 301)
        log.info("Redirecting [301] to: %s" % uri)
        return False


    def head(self, node):

        self.get(node) #Get the page

        #Clear the request & payload and only put the headers and status back
        hdrs = self.response.headers.copy()
        stat = self.response.status
        self.response.clear()
        self.response.headers = hdrs
        self.response.status = stat
        return

    def get(self, node):
        if not self.setupHostinfo(node):
            return

        if not node or node == "":
            node = "/"
        NotModified = False
        etag = etagSlug + str(hash(node))

        try:
            if ( "If-None-Match" in self.request.headers and
                 self.request.headers["If-None-Match"] == etag ):
                    NotModified = True
                    log.debug("Etag - do 304")
            elif ( "If-Unmodified-Since" in self.request.headers and
                   datetime.datetime.strptime(self.request.headers["If-Unmodified-Since"],"%a, %d %b %Y %H:%M:%S %Z") == modtime ):
                    NotModified = True
                    log.debug("Unmod-since - do 304")
        except Exception as e:
            log.info("ERROR reading request headers: %s" % e)
            pass

        retHdrs = None
        if NotModified and DataCache.get(etag):
            retHdrs = DataCache.get(etag)   #Already cached headers for this request
        else:
            self._get(node) #Go build the page
            if self.response.status.startswith("200"):
                self.response.headers.add_header("ETag", etag)
                self.response.headers['Last-Modified'] = modtime.strftime("%a, %d %b %Y %H:%M:%S UTC")
            retHdrs = self.response.headers.copy()
            DataCache.put(etag,retHdrs) #Cache these headers for a future 304 return

        if NotModified:
            self.response.clear()
            self.response.headers = retHdrs
            self.response.set_status(304,"Not Modified")
        return


    def _get(self, node):

        """Get a schema.org site page generated for this node/term.

        Web content is written directly via self.response.

        CORS enabled all URLs - we assume site entirely public.
        See http://en.wikipedia.org/wiki/Cross-origin_resource_sharing

        These should give a JSON version of schema.org:

            curl --verbose -H "Accept: application/ld+json" http://localhost:8080/docs/jsonldcontext.json
            curl --verbose -H "Accept: application/ld+json" http://localhost:8080/docs/jsonldcontext.json.txt
            curl --verbose -H "Accept: application/ld+json" http://localhost:8080/

        Per-term pages vary for type, property and enumeration.

        Last resort is a 404 error if we do not exactly match a term's id.

        See also https://webapp-improved.appspot.com/guide/request.html#guide-request
        """

        global_vars.time_start = datetime.datetime.now()
        tick() #keep system fresh

        self.callCount()

        self.emitHTTPHeaders(node)

        if (node in silent_skip_list):
            return

        if ENABLE_HOSTED_EXTENSIONS:
            layerlist = self.setupExtensionLayerlist(node) # e.g. ['core', 'bib']
        else:
            layerlist = ["core"]

        setSiteName(self.getExtendedSiteName(layerlist)) # e.g. 'bib.schema.org', 'schema.org'
        log.debug("EXT: set sitename to %s " % getSiteName())
        if(node == "_ah/warmup"):
            if "localhost" in os.environ['SERVER_NAME'] and WarmupState.lower() == "auto":
                log.info("Warmup dissabled for localhost instance")
            else:
                self.warmup()
            return
        else:  #Do a bit of warming on each call
            global WarmedUp
            global Warmer
            if not WarmedUp:
                Warmer.stepWarm()

        if(node == "_ah/start"):
            log.info("Instance[%s] received Start request at %s" % (modules.get_current_instance_id(), global_vars.time_start) )
            return

        if (node in ["", "/"]):
            if self.handleHomepage(node):
                return
            else:
                log.info("Error handling homepage: %s" % node)
                return

        if node in ["docs/jsonldcontext.json.txt", "docs/jsonldcontext.json"]:
            if self.handleJSONContext(node):
                return
            else:
                log.info("Error handling JSON-LD context: %s" % node)
                return

        if (node == "docs/full.html"): # DataCache.getDataCache.get
            if self.handleFullHierarchyPage(node, layerlist=layerlist):
                return
            else:
                log.info("Error handling full.html : %s " % node)
                return

        if (node == "docs/schemas.html"): # DataCache.getDataCache.get
            if self.handleSchemasPage(node, layerlist=layerlist):
                return
            else:
                log.info("Error handling schemas.html : %s " % node)
                return



        if (node == "docs/tree.jsonld" or node == "docs/tree.json"):
            if self.handleJSONSchemaTree(node, layerlist=ALL_LAYERS):
                return
            else:
                log.info("Error handling JSON-LD schema tree: %s " % node)
                return

        if (node == "version/2.0/" or node == "version/latest/" or "version/" in node):
            if self.handleFullReleasePage(node, layerlist=layerlist):
                return
            else:
                log.info("Error handling full release page: %s " % node)
                if self.handle404Failure(node):
                    return
                else:
                    log.info("Error handling 404 under /version/")
                    return

        if(node == "_siteDebug"):
            if(getBaseHost() != "schema.org" or os.environ['PRODSITEDEBUG'] == "True"):
                self.siteDebug()
                return

        # Pages based on request path matching a Unit in the term graph:
        if self.handleExactTermPage(node, layers=layerlist):
            return
        else:
            log.info("Error handling exact term page. Assuming a 404: %s" % node)

            # Drop through to 404 as default exit.
            if self.handle404Failure(node):
                return
            else:
                log.info("Error handling 404.")
                return

    def siteDebug(self):
        global STATS
        page = templateRender('siteDebug.tpl')

        self.response.out.write( page )
        ext = getHostExt()
        if ext == "":
            ext = "core"
        self.response.out.write("<div style=\"display: none;\">\nLAYER:%s\n</div>" % ext)
        self.response.out.write("<table style=\"width: 70%; border: solid 1px #CCCCCC; border-collapse: collapse;\"><tbody>\n")
        self.writeDebugRow("Setting","Value",True)
        if SHAREDSITEDEBUG:
            self.writeDebugRow("System start",memcache.get("SysStart"))
            inst = memcache.get("Instances")
            extinst = memcache.get("ExitInstances")
            self.writeDebugRow("Running instances(%s)" % len(memcache.get("Instances")),inst.keys())
            self.writeDebugRow("Instance exits(%s)" % len(memcache.get("ExitInstances")),extinst.keys())
        self.writeDebugRow("httpScheme",getHttpScheme())
        self.writeDebugRow("host_ext",getHostExt())
        self.writeDebugRow("basehost",getBaseHost())
        self.writeDebugRow("hostport",getHostPort())
        self.writeDebugRow("sitename",getSiteName())
        self.writeDebugRow("debugging",getAppVar('debugging'))
        self.writeDebugRow("intestharness",getInTestHarness())
        if SHAREDSITEDEBUG:
            self.writeDebugRow("total calls",memcache.get("total"))
            for s in ALL_LAYERS:
                self.writeDebugRow("%s calls" % s, memcache.get(s))
            for s in ["http","https"]:
                self.writeDebugRow("%s calls" % s, memcache.get(s))


        self.writeDebugRow("This Instance ID",os.environ["INSTANCE_ID"],True)
        self.writeDebugRow("Instance Calls", callCount)
        self.writeDebugRow("Instance Memory Usage [Mb]", str(runtime.memory_usage()).replace("\n","<br/>"))
        self.writeDebugRow("Instance Current DataCache", DataCache.getCurrent())
        self.writeDebugRow("Instance DataCaches", len(DataCache.keys()))
        for c in DataCache.keys():
           self.writeDebugRow("Instance DataCache[%s] size" % c, len(DataCache.getCache(c) ))
        self.response.out.write("</tbody><table><br/>\n")
        self.response.out.write( "</div>\n<body>\n</html>" )

    def writeDebugRow(self,term,value,head=False):
        rt = "td"
        cellStyle = "border: solid 1px #CCCCCC; vertical-align: top; border-collapse: collapse;"
        if head:
            rt = "th"
            cellStyle += " color: #FFFFFF; background: #888888;"

        leftcellStyle = cellStyle
        leftcellStyle += " width: 35%"

        divstyle = "width: 100%; max-height: 100px; overflow: auto"

        self.response.out.write("<tr><%s style=\"%s\">%s</%s><%s style=\"%s\"><div style=\"%s\">%s</div></%s></tr>\n" % (rt,leftcellStyle,term,rt,rt,cellStyle,divstyle,value,rt))

    def callCount(self):
        global instance_first
        global instance_num
        global callCount
        callCount += 1
        if(instance_first):
            instance_first = False
            instance_num += 1
            if SHAREDSITEDEBUG:
                if(memcache.add(key="Instances",value={})):
                    memcache.add(key="ExitInstances",value={})
                    memcache.add(key="http",value=0)
                    memcache.add(key="https",value=0)
                    memcache.add(key="total",value=0)
                    for i in ALL_LAYERS:
                        memcache.add(key=i,value=0)

                Insts = memcache.get("Instances")
                Insts[os.environ["INSTANCE_ID"]] = 1
                memcache.replace("Instances",Insts)

        if SHAREDSITEDEBUG:
            memcache.incr("total")
            memcache.incr(getHttpScheme())
            if getHostExt() != "":
                memcache.incr(getHostExt())
            else:
                memcache.incr("core")


    def warmup(self):
        global WarmedUp
        global Warmer
        if WarmedUp:
            return
        log.info("Instance[%s] received Warmup request at %s" % (modules.get_current_instance_id(), global_vars.time_start) )
        Warmer.warmAll()
        log.info("Instance[%s] completed Warmup request at %s" % (modules.get_current_instance_id(), global_vars.time_start) )

class WarmupTool():

    def __init__(self):
        self.types = []
        self.props = []
        self.enums = []

    def stepWarm(self,all=False):
        global WarmedUp
        if WarmedUp:
            return
        if len (self.types) < len(ALL_LAYERS):
            for l in ALL_LAYERS:
                if l not in self.types:
                    self.types.append(l)
                    GetAllTypes(l)
                    break
        elif len (self.props) < len(ALL_LAYERS):
            for l in ALL_LAYERS:
                if l not in self.props:
                    self.props.append(l)
                    GetAllProperties(l)
                    break
        elif len (self.enums) < len(ALL_LAYERS):
            for l in ALL_LAYERS:
                if l not in self.enums:
                    self.enums.append(l)
                    GetAllEnumerationValues(l)
                    break
        else:
            WarmedUp = True

    def warmAll(self):
        global WarmedUp
        while not WarmedUp:
            self.stepWarm(True)

Warmer = WarmupTool()

def templateRender(templateName,values=None):
    global sitemode #,sitename
    extDef = Unit.GetUnit(getNss(getHostExt()),True)
    extComment = ""
    extVers = ""
    extName = ""
    log.info("EXDEF '%s'" % extDef)
    if extDef:
        extComment = GetComment(extDef,ALL_LAYERS)
        if extComment == "No comment":
            extComment = ""
        extDDs = GetTargets(Unit.GetUnit("disambiguatingDescription", True), extDef, layers=ALL_LAYERS )
        if len(extDDs) > 0:
            extDD = MD.parse(extDDs[0]) 
        else:
            extDD = ""
        first = True
        for ver in GetsoftwareVersions(extDef, ALL_LAYERS):
            if first:
                first = False
                extVers = "<em>(Extension version: "
            else:
                extVers += ", "
            extVers += MD.parse(ver)
        if len(extVers) :
            extVers += ")</em>"
        nms = GetTargets(Unit.GetUnit("name", True), extDef, layers=ALL_LAYERS )
        if len(nms) > 0:
            extName = nms[0]
    
    defvars = {
        'ENABLE_HOSTED_EXTENSIONS': ENABLE_HOSTED_EXTENSIONS,
        'SCHEMA_VERSION': SCHEMA_VERSION,
        'sitemode': sitemode,
        'sitename': getSiteName(),
        'staticPath': makeUrl("",""),
        'myhost': getHost(),
        'myport': getHostPort(),
        'mybasehost': getBaseHost(),
        'host_ext': getHostExt(),
        'extComment': extComment,
        'extDD': extDD,
        'extVers': extVers,
        'extName': extName,
        'debugging': getAppVar('debugging')
    }

    if values:
        defvars.update(values)
    template = JINJA_ENVIRONMENT.get_template(templateName)
    return template.render(defvars)
    
    
def my_shutdown_hook():
    global instance_num
    if SHAREDSITEDEBUG:
        Insts = memcache.get("ExitInstances")
        Insts[os.environ["INSTANCE_ID"]] = 1
        memcache.replace("ExitInstances",Insts)

        memcache.add("Exits",0)
        memcache.incr("Exits")
    log.info("Instance[%s] shutting down" % modules.get_current_instance_id())

runtime.set_shutdown_hook(my_shutdown_hook)

ThreadVars = threading.local()
def getAppVar(var):
    ret = getattr(ThreadVars, var, None)
    log.debug("got var %s as %s" % (var,ret))
    return ret

def setAppVar(var,val):
    log.debug("Setting var %s to %s" % (var,val))
    setattr(ThreadVars,var,val)

def setHttpScheme(val):
    setAppVar('httpScheme',val)

def getHttpScheme():
    return getAppVar('httpScheme')

def setHostExt(val):
    setAppVar('host_ext',val)

def getHostExt():
    return getAppVar('host_ext')

def setSiteName(val):
    setAppVar('sitename',val)

def getSiteName():
    return getAppVar('sitename')

def setHost(val):
    setAppVar('myhost',val)

def getHost():
    return getAppVar('myhost')

def setBaseHost(val):
    setAppVar('mybasehost',val)

def getBaseHost():
    return getAppVar('mybasehost')

def setHostPort(val):
    setAppVar('myport',val)

def getHostPort():
    return getAppVar('myport')

def makeUrl(ext="",path=""):
        port = ""
        sub = ""
        p = ""
        if(getHostPort() != "80"):
            port = ":%s" % getHostPort()
        if ext != "core" and ext != "":
            sub = "%s." % ext
        if path != "":
            if path.startswith("/"):
                p = path
            else:
                p = "/%s" % path

        url = "%s://%s%s%s%s" % (getHttpScheme(),sub,getBaseHost(),port,p)
        return url

#log.info("STARTING UP... reading schemas.")
#load_graph(loadExtensions=ENABLE_HOSTED_EXTENSIONS)
read_schemas(loadExtensions=ENABLE_HOSTED_EXTENSIONS)
if ENABLE_HOSTED_EXTENSIONS:
    read_extensions(ENABLED_EXTENSIONS)
schemasInitialized = True

app = ndb.toplevel(webapp2.WSGIApplication([("/(.*)", ShowUnit)]))
back to top