https://github.com/schemaorg/schemaorg
Raw File
Tip revision: 3a6f7bf7fba215323eaacc965bd4338d87d07c68 authored by Dataliberate on 11 February 2019, 10:34:06 UTC
Tweak
Tip revision: 3a6f7bf
sdoapp.py
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
from __future__ import with_statement

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

import os
import re
import webapp2
import urllib2
import mimetypes
import jinja2
import logging
import StringIO
import json
import rdflib
#from rdflib.namespace import RDFS, RDF, OWL
#from rdflib.term import URIRef

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

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 google.appengine.api import app_identity
from google.appengine.api.modules import modules

GAE_APP_ID = "appId"
GAE_VERSION_ID = "versionId"


#Testharness Used to indicate we are being called from tests - use setInTestHarness() & getInTestHarness() to manage value - defauluts to False (we are not in tests)

from testharness import *
from sdoutil import *
from api import *
from apirdfterm import *
from apirdflib import load_graph, getNss, getRevNss, buildSingleTermGraph, serializeSingleTermGrapth
from apirdflib import countTypes, countProperties, countEnums, graphFromFiles, getPathForPrefix, getPrefixForPath, rdfgettops

from apimarkdown import Markdown

from sdordf2csv import sdordf2csv
CONFIGFILE = os.environ.get("CONFIGFILE","sdoconfig.json")
SdoConfig.load(CONFIGFILE)

SCHEMA_VERSION="3.4"

if not getInTestHarness():
    GAE_APP_ID = app_identity.get_application_id()
    GAE_VERSION_ID = modules.get_current_version_name()

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", "3.0": "2016-05-04", "3.1": "2016-08-09", "3.2": "2017-03-23", "3.3": "2017-08-14", "3.4": "2018-06-15" }

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

all_layers = {}
ext_re = re.compile(r'([^\w,])+')
validNode_re = re.compile(r'^[\w\/.-]+$')

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

TEMPLATESDIR = SdoConfig.templateDir()
FileBasedTemplates = True

def urlTemplateLoader(name):
    log.info("TEMPLATE LOADER LOOKING FOR: %s" % name)
    url = TEMPLATESDIR + "/" + name
    log.info("URL: %s" % url)
    try:
        fd = urllib2.urlopen(url)
        res = fd.read()
    except urllib2.URLError as e:
        log.info("URLError %s" % e)
        return None
    return res
    
    
    
if TEMPLATESDIR:
    if TEMPLATESDIR.startswith("file://"):
        TEMPLATESDIR = TEMPLATESDIR[7:]
    if "://" in TEMPLATESDIR:
        FileBasedTemplates = False
else:
    TEMPLATESDIR = os.path.join(os.path.dirname(__file__), 'templates')
    log.info("No Templates directory defined - defaulting to %s" % TEMPLATESDIR)

if FileBasedTemplates:
    JINJA_ENVIRONMENT = jinja2.Environment(loader=jinja2.FileSystemLoader(TEMPLATESDIR),
    extensions=['jinja2.ext.autoescape'], autoescape=True, cache_size=0)
else:
    JINJA_ENVIRONMENT = jinja2.Environment(loader=jinja2.FunctionLoader(urlTemplateLoader),
    extensions=['jinja2.ext.autoescape'], autoescape=True, cache_size=0)
    


CANONICALSCHEME = "http"
ENABLE_JSONLD_CONTEXT = True
ENABLE_CORS = True
ENABLE_HOSTED_EXTENSIONS = True
DISABLE_NDB_FOR_LOCALHOST = True
ENABLEMOREINFO = True
WORKINGHOSTS = ["schema.org","schemaorg.appspot.com",
                "webschemas.org","webschemas-g.appspot.com",
                "sdo-test.appspot.com",
                "localhost"]
EXTENSION_SUFFIX = "" # e.g. "*"

CORE = 'core'
ATTIC = 'attic'
ENABLED_EXTENSIONS = [ATTIC, 'auto', 'bib', 'health-lifesci', 'pending', 'meta', 'iot' ]
#### Following 2 lines look odd - leave them as is - just go with it!
ALL_LAYERS = [CORE,'']
ALL_LAYERS += ENABLED_EXTENSIONS
####
ALL_LAYERS_NO_ATTIC = list(ALL_LAYERS)
ALL_LAYERS_NO_ATTIC.remove(ATTIC)
setAllLayersList(ALL_LAYERS)

OUTPUTDATATYPES = [".csv",".jsonld",".ttl",".rdf",".xml",".nt"]

FORCEDEBUGGING = False
# FORCEDEBUGGING = True

SHAREDSITEDEBUG = True
if getInTestHarness():
    SHAREDSITEDEBUG = False

LOADEDSOURCES = False

noindexpages = True

############# Warmup Control ########
WarmedUp = False
WarmupState = "Auto"
if "WARMUPSTATE" in os.environ:
    WarmupState = os.environ["WARMUPSTATE"]
log.info("[%s] WarmupState: %s" % (getInstanceId(short=True),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"]

def getAppEngineVersion():
    ret = ""
    if not getInTestHarness():
        from google.appengine.api.modules.modules import get_current_version_name
        ret = get_current_version_name()
        #log.info("AppEngineVersion '%s'" % ret)
    else:
        return "TestVersion"
    return ret

instance_first = True
instance_num = 0
callCount = 0
global_vars = threading.local()
starttime = datetime.datetime.utcnow()
systarttime = starttime
modtime = starttime
etagSlug = ""

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

class SlugEntity(ndb.Model):
    slug = ndb.StringProperty()
    modtime = ndb.DateTimeProperty()

def setmodiftime(sttime):
    global modtime, etagSlug
    if not getInTestHarness():
        modtime = sttime.replace(microsecond=0)
        etagSlug = "24751%s" % modtime.strftime("%y%m%d%H%M%Sa")
        log.debug("set slug: %s" % etagSlug)
        slug = SlugEntity(id="ETagSlug",slug=etagSlug, modtime=modtime)
        slug.put()

def getmodiftime():
    global modtime, etagSlug
    if not getInTestHarness():
        slug = SlugEntity.get_by_id("ETagSlug")
        if not slug:#Occationally memcache will loose the value and result in  becomming Null value
            systarttime = datetime.datetime.utcnow()
            tick()
            setmodiftime(systarttime)#Will store it again
            slug = SlugEntity.get_by_id("ETagSlug")
        modtime = slug.modtime
        etagSlug = str(slug.slug)
    return modtime

def getslug():
    global etagSlug
    getmodiftime()
    return etagSlug


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)

def check4NewVersion():
    ret = False
    dep = None
    try:
        fpath = os.path.join(os.path.split(__file__)[0], 'admin/deploy_timestamp.txt')
        #log.info("fpath: %s" % fpath)
        with open(fpath, 'r') as f:
            dep = f.read()
            dep = dep.replace("\n","")
        f.close()
    except Exception  as e:
        log.info("ERROR reading: %s" % e)
        pass    

    if  getInTestHarness() or "localhost" in os.environ['SERVER_NAME']: #Force new version logic for local versions and tests
        ret = True
        log.info("Assuming new version for local/test instance")
    else:
        stored,info = getTimestampedInfo("deployed-timestamp")
        if stored != dep:
            ret = True
    
    return ret, dep

def storeNewTimestamp(stamp=None):
    storeTimestampedInfo("deployed-timestamp",stamp)

def storeInitialisedTimestamp(stamp=None):
    storeTimestampedInfo("initialised-timestamp",stamp)



if getInTestHarness():
    load_examples_data(ENABLED_EXTENSIONS)
else: #Ensure clean start for any memcached or ndb store values...

    changed, dep = check4NewVersion()
    if changed: #We are a new instance of the app
        msg = "New app instance [%s:%s] detected - FLUSHING CACHES.  (deploy_timestamp='%s')\nLoaded Config file from: %s" % (GAE_VERSION_ID,GAE_APP_ID,dep,CONFIGFILE)
        memcache.flush_all()
        storeNewTimestamp(dep)

        sdo_send_mail(to="rjw@dataliberate.com",subject="[SCHEMAINFO] from 'sdoapp'", msg=msg)
        log.info("%s" % msg)

        load_start = datetime.datetime.now()
        systarttime = datetime.datetime.utcnow()
        memcache.set(key="app_initialising", value=True, time=300)  #Give the system 5 mins - auto remove flag in case of crash
        memcache.set(key="static-version", value=appver)
        memcache.add(key="SysStart", value=systarttime)
        instance_first = True
        cleanmsg = CacheControl.clean()
        log.info("Clean count(s): %s" % cleanmsg)
        log.info(("[%s] Cache clean took %s " % (getInstanceId(short=True),(datetime.datetime.now() - load_start))))
        load_start = datetime.datetime.now()
        tick()
        memcache.set(key="app_initialising", value=False)
        log.debug("[%s] Awake >>>>>>>>>>>." % (getInstanceId(short=True)))
        storeInitialisedTimestamp()
    else:
        time.sleep(0.5) #Give time for the initialisation flag (possibly being set in another thread/instance) to be set
        WAITCOUNT = 180
        waittime = WAITCOUNT
        while waittime > 0:
            waittime -= 1
            flag = memcache.get("app_initialising")
            if not flag or flag == False: #Initialised or value missing
                break

            log.debug("[%s] Waited %s seconds for intialisation to end memcahce value = %s" % (getInstanceId(short=True),
                                                    (WAITCOUNT - waittime),memcache.get("app_initialising")))
            time.sleep(1)
        if waittime <= 0:
            log.info("[%s] Waited %s seconds for intialisation to end - proceeding anyway!"  % (getInstanceId(short=True),WAITCOUNT))

        log.debug("[%s] End of waiting !!!!!!!!!!." % (getInstanceId(short=True)))
        tick()
        systarttime = memcache.get("SysStart")

    if(not systarttime): #Occationally memcache will loose the value and result in systarttime becomming Null value
         systarttime = datetime.datetime.utcnow()
         tick()

    setmodiftime(systarttime)

#################################################

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, term, depth = 1, hashorslash="/", layers='core', idprefix="", urlprefix="", traverseAllLayers=False, buff=None):

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

        #log.info("traverseForHTML: node=%s hashorslash=%s" % ( term, hashorslash ))

        if not term:
            return False

        if term.superseded() or term.getLayer() == ATTIC:
            return False

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

        home = term.getLayer()
        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 = term.getSubs()
        idstring = idprefix + term.getId()
        if len(subTypes) > 0:
            # and we haven't been here before
            if term.getId() not in self.visited:
                self.emit2buff(buff, ' %s<li class="tbranch" id="%s"><a %s %s href="%s%s%s">%s</a>%s' % (" " * 4 * depth, idstring,  tooltip, extclass, urlprefix, hashorslash, term.getId(), term.getId(), 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, idprefix=idprefix, urlprefix=urlprefix, traverseAllLayers=traverseAllLayers,buff=subBuff)
                    if got:
                        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
                seencount = self.visited.count(term.getId())
                idstring = "%s%s" % (idstring, "+" * seencount)
                seen = '  <a href="#%s">+</a> ' % term.getId()
                self.emit2buff(buff, ' %s<li class="tbranch" id="%s"><a %s %s href="%s%s%s">%s</a>%s%s' % (" " * 4 * depth, idstring,  tooltip, extclass, urlprefix, hashorslash, term.getId(), term.getId(), extflag, seen) )
        # leaf nodes
        if len(subTypes) == 0:
            gotOutput = True
            seen = ""
            if term.getId() in self.visited:
                seencount = self.visited.count(term.getId())
                idstring = "%s%s" % (idstring, "+" * seencount)
                seen = '  <a href="#%s">+</a> ' % term.getId()

            self.emit2buff(buff, '%s<li class="tleaf" id="%s"><a %s %s href="%s%s%s">%s</a>%s%s' % (" " * depth, idstring, tooltip, extclass, urlprefix, hashorslash, term.getId(), term.getId(), extflag, seen ))

        self.visited.append(term.getId()) # remember our visit
        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 not node or not node.id:
            log.error("Error None value passed to traverseForJSONLD()")
            return
        if node.id in self.visited:
            # self.emit("skipping %s - already visited" % node.id)
            return
        self.visited.append(node.id)
        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(term, layers='core'):
    """Returns the examples (if any) for some Unit node."""
    return LoadTermExamples(term)

def GetExtMappingsRDFa(term):
    """Self-contained chunk of RDFa HTML markup with mappings for this term."""
    equivs = term.getEquivalents()
    if (term.isClass()):
        if len(equivs) > 0:
            markup = ''
            for c in equivs:

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

            return markup
    if (term.isProperty()):
        if len(equivs) > 0:
            markup = ''
            for c in equivs:
                markup = markup + "<link property=\"owl:equivalentProperty\" href=\"%s\"/>\n" % c
            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 emitCacheHeaders(self):
        """Send cache-related headers via HTTP."""
        if "CACHE_CONTROL" in os.environ:
            log.info("Setting http cache control to '%s' from .yaml" % os.environ["CACHE_CONTROL"])
            self.response.headers['Cache-Control'] = os.environ["CACHE_CONTROL"]
        else:
            self.response.headers['Cache-Control'] = "public, max-age=600" # 10m
        self.response.headers['Vary'] = "Accept, Accept-Encoding"

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


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

        # if we think we have more info on this term, show a bulleted list of extra items.
        moreblock = os.environ.get("MOREBLOCK")
        if not moreblock or (moreblock.lower() == "false"):
            return ""
        

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


        feedback_url = FEEDBACK_FORM_BASE_URL.format(term.getUri, term.getType())
        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(term.getId())

        ]
        if term.getLayer() != "core":
            items.append("'{0}' is mentioned in the <a href='{1}'>{2}</a> extention.".format( term.getId(), makeUrl(term.getLayer(),"",full=True), term.getLayer() ))

        moreinfo = """<div>
        <div id='infobox' style='text-align: right;' role="checkbox" aria-checked="false"><label 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 getParentNames(self, nodeName, layers):

        ret = [nodeName]
        node = Unit.GetUnit(nodeName)
        if node and node.id:
            sc = Unit.GetUnit("rdfs:subClassOf")
            targs = GetTargets(sc, node, layers=layers)
            if targs:
                for p in targs:
                    ret.extend(self.getParentNames(p.id, layers=layers))


        return ret

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

        if (node not in self.parentStack):
            self.parentStack.append(node)

        if (Unit.isAttribute(node, layers=layers)):
            self.parentStack.append(Unit.GetUnit("schema: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, term, 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 not term:
            return ""

        if ":" in term.getId():
            return self.external_ml(term)

        if label=='':
          label = term.getLabel()
        if title != '':
          title = " title=\"%s\"" % (title)
        if prop:
            prop = " property=\"%s\"" % (prop)

        rdfalink = ''
        if prop:
            rdfalink = '<link %s href="%s%s" />' % (prop,api.SdoConfig.vocabUri(),label)

        if(term.id == "DataType"):  #Special case
            return "%s<a href=\"%s\">%s</a>" % (rdfalink,term.getId(), term.getId())

        urlprefix = "."
        home = term.getLayer()

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

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

        return "%s<a %s %s href=\"%s%s%s\"%s>%s</a>%s" % (rdfalink,tooltip, extclass, urlprefix, hashorslash, term.getId(), 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 external_ml(self, term):
        #log.info("EXTERNAL!!!! %s %s " % (term.getLabel(),term.getId()))

        name = term.getId()
            
        if not ":" in name:
            return name
            
        if name.startswith("http") and '#' in name:
            x = name.split("#")
            path = x[0] + "#"
            val = x[1]
            voc = getPrefixForPath(path)
        
            
        elif name.startswith("http"):
            val = os.path.basename(name)
            path = name[:len(name) - len(val)]
            voc = getPrefixForPath(path)

        else:
            x = name.split(":")
            voc = x[0]
            val = x[1]
            path = getPathForPrefix(voc)
            if path:
                if not path.endswith("#") and not path.endswith("/"):
                    path += "/"
        return "<a href=\"%s%s\" class=\"externlink\" target=\"_blank\">%s:%s</a>" % (path,val,voc,val)



    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, term, layers='core'):
        """Write out the HTML page headers for this node."""
        self.write("<h1 property=\"rdfs:label\" class=\"page-title\">")
        self.write(term.getLabel())
        self.write("</h1>\n")
        home = term.getLayer()
        if home != "core" and home != "":
            if home == ATTIC:
                self.write("Defined in the %s.schema.org archive area.<br/><strong>Use of this term is not advised</strong><br/>" % home)
            else:
                self.write("Defined in the %s.schema.org extension.<br/>" % home)
        self.emitCanonicalURL(term)

        self.BreadCrumbs(term)

        comment = term.getComment()

        self.write(" <div property=\"rdfs:comment\">%s</div>\n\n" % (comment) + "\n")
        usage = GetUsage(term.getId())
        if len(usage):
            self.write(" <br/><div>Usage: %s</div>\n\n" % (usage) + "\n")

        if ENABLEMOREINFO:
            self.write(self.moreInfoBlock(term))

    def emitCanonicalURL(self,term):
        site = SdoConfig.vocabUri()
        if site != "http://schema.org":
            cURL = "%s%s" % (site,term.getId())
            self.write(" <span class=\"canonicalUrl\">Canonical URL: %s</span> " % (cURL))
        else:

            cURL = "%s://schema.org/%s" % (CANONICALSCHEME,term.getId())
            if CANONICALSCHEME == "http":
                other = "https"
            else:
                other = "http"
            sa = '\n<link  property="sameAs" href="%s://schema.org/%s" />' % (other,term.getId())

            self.write(" <span class=\"canonicalUrl\">Canonical URL: <a href=\"%s\">%s</a></span> " % (cURL, cURL))
            self.write(sa)

    # Stacks to support multiple inheritance
    crumbStacks = []
    def BreadCrumbs(self, term):
        self.crumbStacks = []
        cstack = []
        self.crumbStacks.append(cstack)
        self.WalkCrumbs(term,cstack)
        
        if term.isProperty():
            cstack.append(VTerm.getTerm("http://schema.org/Property"))
            cstack.append(VTerm.getTerm("http://schema.org/Thing"))
        elif term.isDataType():
            cstack.append(VTerm.getTerm("http://schema.org/DataType"))


        enuma = term.isEnumerationValue()

        crumbsout = []
        for row in range(len(self.crumbStacks)):
           thisrow = ""
           targ = self.crumbStacks[row][len(self.crumbStacks[row])-1]
           if not targ:
                continue
           count = 0
           while(len(self.crumbStacks[row]) > 0):
                propertyval = None
                n = self.crumbStacks[row].pop()

                if((len(self.crumbStacks[row]) == 1) and n and
                    not ":" in n.id) : #penultimate crumb that is not a non-schema reference
                    if term.isProperty():
                        if n.isProperty(): #Can only be a subproperty of a property
                            propertyval = "rdfs:subPropertyOf"
                    else:
                        propertyval = "rdfs:subClassOf"

                if(count > 0):
                    if((len(self.crumbStacks[row]) == 0) and enuma): #final crumb
                        thisrow += " :: "
                    else:
                        thisrow += " &gt; "
                count += 1
                thisrow += "%s" % (self.ml(n,prop=propertyval))
           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, term, cstack):
        if ":" in term.getId():  #Suppress external class references
            return

        cstack.append(term)
        tmpStacks = []
        tmpStacks.append(cstack)
        supers = term.getSupers()
        
        for i in range(len(supers)):
            if(i > 0):
                t = cstack[:]
                tmpStacks.append(t)
                self.crumbStacks.append(t)
        x = 0

        for p in supers:
            self.WalkCrumbs(p,tmpStacks[x])
            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("schema: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("schema: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 hideAtticTerm(self,term):
        if getHostExt() == ATTIC:
            return False
        if term.inLayers([ATTIC]):
            return True
        return False

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

        headerPrinted = False
        props = cl.getProperties()

        for prop in props:
            if prop.superseded() or self.hideAtticTerm(prop):
                continue
            olderprops = prop.getSupersedes()
            inverseprop = prop.getInverseOf()
            ranges = prop.getRanges()
            doms = prop.getDomains()
            comment = prop.getComment()
            if ":" in prop.id and comment == "-":
                comment = "Term from external vocabulary"
            if not getAppVar("tableHdr"):
                setAppVar("tableHdr",True)
                if (term.isClass() and not term.isDataType() and term.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")
                self.tablehdr = True
            if (not headerPrinted):
                class_head = self.ml(cl)
                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=\"%s\">\n    \n      <th class=\"prop-nam\" scope=\"row\">\n\n<code property=\"rdfs:label\">%s</code>\n    </th>\n " % (prop.getUri(), self.ml(prop)))
            out.write("<td class=\"prop-ect\">\n")
            first_range = True
            for r in ranges:
                if (not first_range):
                    out.write(" or <br/> ")
                first_range = False
                out.write(self.ml(r, prop='rangeIncludes'))
                out.write("&nbsp;")
            for d in doms:
                out.write("<link property=\"domainIncludes\" href=\"%s\">" % d.getUri())
            out.write("</td>")
            out.write("<td class=\"prop-desc\" property=\"rdfs:comment\">%s" % (comment))
            if (olderprops and 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\"></td></tr>")

        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 elsewhere</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("schema:domainIncludes")

        targetlayers=self.appropriateLayers(layers)
        #log.info("Appropriate targets %s" % targetlayers)
        exts = {}

        for prop in sorted(GetSources(di, cl, targetlayers), key=lambda u: u.id):
            if ":" in prop.id:
                continue
            if (prop.superseded(layers=targetlayers)):
                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()

            if not ext in exts.keys():
                exts[ext] = []
            exts[ext].append(prop)

        for e in sorted(exts.keys()):
            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, term, out=None, hashorslash="/"):
        """Write out a table of incoming properties for a per-type page."""
        if not out:
            out = self

        headerPrinted = False
        props = term.getTargetOf()
        for prop in props:
            if (prop.superseded()):
                continue
            supersedes = prop.getSupersedes()
            inverseprop = prop.getInverseOf()
            ranges = prop.getRanges()
            domains = prop.getDomains()
            comment = prop.getComment()

            if (not headerPrinted):
                self.write("<br/><br/><div id=\"incoming\">Instances of %s may appear as values for the following properties</div><br/>" % (self.ml(term)))
                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_dom = True
            for d in domains:
                if (not first_dom):
                    self.write(" or<br/> ")
                first_dom = False
                self.write(self.ml(d))
                self.write("&nbsp;")
            self.write("</td>")
            self.write("<td class=\"prop-desc\">%s " % (comment))
            if supersedes:
                self.write(" Supersedes")
                first = True
                for s in supersedes:
                    if first:
                        first = False
                        self.write(",")
                    self.write(" %s" % self.ml(s))
                self.write(". ")
            if inverseprop:
                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("schema: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, term, out=None, hashorslash="/"):
        """Write out properties of this property, for a per-property page."""
        if not out:
            out = self

        ranges = term.getRanges()
        domains =term.getDomains()

        inverseprop = term.getInverseOf()
        subprops = term.getSubs()
        superprops = term.getSupers()


        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.getId(),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      ")

        first_range = True
        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." % (term.getId(), r.getId())
            out.write(" <code>%s</code> " % (self.ml(r, r.getId(), 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." % (term.getId(), d.getId())
            out.write("\n    <code>%s</code> " % (self.ml(d, d.getId(), tt, prop="domainIncludes",hashorslash=hashorslash)+"\n" ))
        out.write("      </td>\n    </tr>\n</table>\n\n")

        # Sub-properties
        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 sp in subprops:
                c = ShortenOnSentence(StripHtmlTags( sp.getComment(s) ),60)
                tt = "%s: ''%s''" % ( sp.getId(), c)
                out.write("\n    <tr><td><code>%s</code></td></tr>\n" % (self.ml(sp, sp.getId(), 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 sp in superprops:
                c = ShortenOnSentence(StripHtmlTags( sp.getComment() ),60)
                tt = "%s: ''%s''" % ( sp.getId(), c)
                out.write("\n    <tr><td><code>%s</code></td></tr>\n" % (self.ml(sp, sp.getId(), tt, hashorslash=hashorslash)))
            out.write("\n</table>\n\n")

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

        if not out:
            out = self
        newerprop = term.getSupersededBy() # None of one. e.g. we're on 'seller'(new) page, we get 'vendor'(old)
        #olderprop = node.supersedes(layers=layers) # None or one
        olderprops = term.getSupersedes()


        # 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 = ShortenOnSentence(StripHtmlTags( o.getComment() ),60)
                tt = "%s: ''%s''" % ( o.getId(), c)
                out.write("\n    <tr><td><code>%s</code></td></tr>\n" % (self.ml(o, o.getId(), tt)))
                log.info("Super %s" % o.getId())
            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")
            c = ShortenOnSentence(StripHtmlTags( newerprop.getComment() ),60)
            tt = "%s: ''%s''" % ( newerprop.getId(), c)
            out.write("\n    <tr><td><code>%s</code></td></tr>\n" % (self.ml(newerprop, newerprop.getId(), tt)))
            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')
        if accept_header:
            accept_header = accept_header.split(',')
        else:
            accept_header = ""

        # 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)):
            self.response.set_status(302,"Found")
            self.response.headers['Location'] = makeUrl("","docs/jsonldcontext.json")
            self.emitCacheHeaders()
            return False #don't cache this redirect
        else:
            # Serve a homepage from template
            # the .tpl has responsibility for extension homepages
            # TODO: pass in extension, base_domain etc.
            #sitekeyedhomepage = "homepage %s" % getSiteName()
            ext = getHostExt()

            if ext == "core":
                ext = ""

            if len(ext):
                ext += "."
            sitekeyedhomepage = "%sindex.html" % ext
            hp = getPageFromStore(sitekeyedhomepage)
            self.response.headers['Content-Type'] = "text/html"
            self.emitCacheHeaders()
            if hp:
                self.response.out.write( hp )
                #log.info("Served datacache homepage.tpl key: %s" % sitekeyedhomepage)
            else:
                template_values = {
                    'ext_contents': self.handleExtensionContents(getHostExt()),
                    'home_page': "True",
                }
                page = templateRender('homepage.tpl', node, template_values)
                self.response.out.write( page )
                log.debug("Served and cached fresh homepage.tpl key: %s " % sitekeyedhomepage)

                setAppVar(CLOUDEXTRAMETA,{'x-goog-meta-sdotermlayer': getHostExt()})
                PageStore.put(sitekeyedhomepage, page)
                #            self.response.out.write( open("static/index.html", 'r').read() )
            return False # - Not caching homepage
        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 not layers or len(layers)==0:
            return "schema.org"
        return (getHostExt() + ".schema.org")

    def emitSchemaorgHeaders(self, node, ext_mappings='', sitemode="default", sitename="schema.org", layers="core"):
        self.response.out.write(self.buildSchemaorgHeaders(node, ext_mappings, sitemode, sitename, layers))


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

        * entry = name of the class or property
        """
        buff = sdoStringIO()

        rdfs_type = 'rdfs:Class'
        entry = term.id
        if term.isProperty():
            rdfs_type = 'rdfs:Property'

        desc = entry
        desc = self.getMetaDescription(term, lengthHint=200)

        template_values = {
            'entry': str(entry),
            'desc' : desc,
            'menu_sel': "Schemas",
            'rdfs_type': rdfs_type,
            'ext_mappings': ext_mappings,
            'noindexpage': noindexpages
        }
        out = templateRender('genericTermPageHeader.tpl', term, template_values)
        buff.write(out)

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

    def buildSchemaorgHeaders(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
        """
        buff = sdoStringIO()

        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'

        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,
            'noindexpage': noindexpages
        }
        out = templateRender('genericTermPageHeader.tpl', node, template_values)
        buff.write(out)

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

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

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

        lengthHint -= len(desc)

        comment = term.getComment()

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

        return desc

    def appropriateLayers(self,layers="core"):
        if ATTIC in layers:
            return ALL_LAYERS
        return ALL_LAYERS_NO_ATTIC

    def emitExactTermPage(self, term, layers="core"):
        """Emit a Web page that exactly matches this node."""
        log.info("EXACT PAGE: %s" % term.getId())
        self.outputStrings = [] # blank slate
        cached = getPageFromStore(term.getId())

        if (cached != None):
            log.info("GOT CACHED page for %s" % term.getId())
            self.response.write(cached)
            return
        log.info("Building page")
        
        ext_mappings = GetExtMappingsRDFa(term)
        self.write(self.buildSiteHeaders(term, ext_mappings, sitemode, getSiteName()))
        
        log.info("Done buildSiteHeaders")
        
        self.emitUnitHeaders(term) # writes <h1><table>...
        stack = self._removeStackDupes(term.getTermStack())
        setAppVar("tableHdr",False)
        if term.isClass():
            for p in stack:
                self.ClassProperties(p, p==[0], out=self, term=term)
            if getAppVar("tableHdr"):
                self.write("\n\n</table>\n\n")
                
            
            self.emitClassIncomingProperties(term)

            #self.emitClassExtensionSuperclasses(node,layers)

            #self.emitClassExtensionProperties(p,layers)

        elif term.isProperty():
            self.emitAttributeProperties(term)
            
        elif term.isDataType():
            self.emitClassIncomingProperties(term)
        
        self.emitSupersedes(term)
        self.emitchildren(term)
        self.emitAcksAndSources(term)
        self.emitTermExamples(term)
        
        self.write(" <br/>\n\n</div>\n</body>\n<!-- AppEngineVersion %s (%s)-->\n</html>" % (getAppEngineVersion(),appver))

        page = "".join(self.outputStrings)
        setAppVar(CLOUDEXTRAMETA,{'x-goog-meta-sdotermlayer': term.getLayer()})
        PageStore.put(term.getId(),page)

        self.response.write(page)
    
    def emitTermExamples(self,term):
        examples = GetExamples(term)
        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.keyvalue):

                #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")
        
        
    def _removeStackDupes(self,stack):
        cleanstack = []
        i = len(stack)
        while i:
            i -= 1
            if not stack[i] in cleanstack:
                cleanstack.insert(0,stack[i])

        return cleanstack

    def emitAcksAndSources(self,term):
        sources = term.getSources()
        if len(sources):
            s = ""
            if len(sources) > 1:
                s = "s"
            self.write("<h4  id=\"acks\">Source%s</h4>\n" % s)
            for val in sources:
                if val.startswith("http://") or val.startswith("https://"):
                    val = "[%s](%s)" % (val,val) #Put into markdown format
                self.write(Markdown.parse(val,True))

        acknowledgements = term.getAcknowledgements()
        if len(acknowledgements):
            s = ""
            if len(acknowledgements) > 1:
                s = "s"
            self.write("<h4  id=\"acks\">Acknowledgement%s</h4>\n" % s)
            for ack in sorted(acknowledgements):
                self.write(Markdown.parse(str(ack),True))
    
        
    def emitchildren(self,term):
            children = term.getSubs()
            
            log.info("CILDREN: %s" % children)

            if (len(children) > 0):
                buff = StringIO.StringIO()
                for c in children:
                    if c.superseded() or self.hideAtticTerm(c):
                        continue
                    buff.write("<li> %s </li>" % (self.ml(c)))

                if (len(buff.getvalue()) > 0):
                    if term.isDataType():
                        self.write("<br/><b>More specific DataTypes</b><ul>")
                    elif term.isClass() or term.isEnumerationValue():
                        self.write("<br/><b>More specific Types</b><ul>")
                    elif term.isProperty():
                        self.write("<br/><b>Sub-properties</b><ul>")
                    elif term.isEnumeration():
                        self.write("<br/><b>Enumeration members</b><ul>")
                    self.write(buff.getvalue())
                    self.write("</ul>")

                buff.close()
        
    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.info("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
        label = "jsonld:jsonldcontext.jsonld"
        jsonldcontext = getPageFromStore(label)
        if not jsonldcontext:
            jsonldcontext = GetJsonLdContext(layers=ALL_LAYERS)

            PageStore.put(label,jsonldcontext)

            if PAGESTOREMODE == "CLOUDSTORE":
                cloudstoreStoreContent("docs/jsonldcontext.json", jsonldcontext, "html")
                cloudstoreStoreContent("docs/jsonldcontext.json.txt", jsonldcontext, "html")

        if (node=="docs/jsonldcontext.json.txt"):
            self.response.headers['Content-Type'] = "text/plain"
            self.emitCacheHeaders()
            self.response.out.write( jsonldcontext )
            return True
        if (node=="docs/jsonldcontext.json"):
            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'):
        page = getPageFromStore(node)
        if page:
            self.response.out.write( page )
            log.debug("Serving recycled SchemasPage.")
            return True
        else:
            self.response.headers['Content-Type'] = "text/html"
            self.emitCacheHeaders()

            extensions = []
            for ex in sorted(ENABLED_EXTENSIONS):
                if ex != ATTIC:
                    extensions.append("<a href=\"%s\">%s.schema.org</a>" % (makeUrl(ex,"",full=True),ex))

            page = templateRender('schemas.tpl', node, {'counts': self.getCounts(),
                                    'extensions': extensions,
                                    'attic': "<a href=\"%s\">%s.schema.org</a>" % (makeUrl(ATTIC,""),ATTIC),
                                    'menu_sel': "Schemas"})

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

            return True

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

        page = getPageFromStore(node)

        if page:
            self.response.out.write( page)
            log.debug("Serving recycled DumpsPage.")
            return True
        else:
            extensions = sorted(ENABLED_EXTENSIONS)

            page = templateRender('developers.tpl', node, {'extensions': extensions,
                                    'version': SCHEMA_VERSION,
                                    'menu_sel': "Schemas"})

            self.response.out.write( page )
            log.debug("Serving fresh DumpsPage.")
            PageStore.put(node,page)

            return True

    def getCounts(self):
        log.info("counts")
        typesCount = str(countTypes(extension="core"))
        log.info("TYPES %s" % typesCount)
        propsCount = str(countProperties(extension="core"))
        log.info("PROPS %s" % propsCount)
        enumCount = str(countEnums(extension="core"))
        log.info("ENUMS %s" % enumCount)

        text = ""
        text += "The core vocabulary currently consists of %s Types, " % typesCount
        text += " %s Properties, " % propsCount
        text += "and %s Enumeration values." % enumCount
        return text


    def handleFullHierarchyPage(self, node,  layerlist='core'):
        #label = 'FullTreePage - %s' % getHostExt()
        #label = 'FullTreePage'
        urlprefix = ''
        label = node
        if label.startswith('docs/'):
            urlprefix = '..'

        if getPageFromStore(label):
            self.response.out.write( getPageFromStore(label) )
            log.debug("Serving recycled %s." % label)
            return True
        else:
            self.response.headers['Content-Type'] = "text/html"
            self.emitCacheHeaders()
            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 vocabulary</h3>"
            local_label = ""
            ext_button = ""

            tops = self.gettops()

            full_thing_tree = ""
            thing_tree = ""
            datatype_tree = ""
            first = True
            dtcount = 0
            tcount = 0
            mainroot = TypeHierarchyTree(local_label)
            dtroot = TypeHierarchyTree("<h4>Data Types</h4>")
            for t in tops:
                if not first:
                    local_label = ""
                first = False
                top = VTerm.getTerm(t)
                if top.isDataType() or top.getUri() == "http://schema.org/DataType":
                    dtcount += 1
                    dtroot.traverseForHTML(top, layers=layerlist, idprefix="D.", urlprefix=urlprefix)
                else:
                    tcount += 1
                    mainroot.traverseForHTML(top, layers=layerlist, idprefix="C.", urlprefix=urlprefix, traverseAllLayers=True)
            if dtcount:
                datatype_tree += dtroot.toHTML()
            if tcount:
                full_thing_tree += mainroot.toHTML()

            #fullmainroot = TypeHierarchyTree("<h3>Core plus all extension vocabularies</h3>")
            #fullmainroot.traverseForHTML(uThing, layers=ALL_LAYERS_NO_ATTIC, idprefix="CE.", urlprefix=urlprefix)
            #full_thing_tree = fullmainroot.toHTML()

            ext_thing_tree = ""
            #if len(extonlylist) > 0:
                #extroot = TypeHierarchyTree("<h3>Extension: %s</h3>" % extlist)
                #extroot.traverseForHTML(uThing, layers=extonlylist, traverseAllLayers=True, idprefix="E.", urlprefix=urlprefix)
                #ext_thing_tree = extroot.toHTML()

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

            full_button = "Core plus all extension vocabularies"

            page = templateRender('full.tpl', node, { 'full_thing_tree': full_thing_tree,
                                    'datatype_tree': datatype_tree,
                                    'menu_sel': "Schemas"})

            self.response.out.write( page )
            log.debug("Serving fresh %s." % label)
            PageStore.put(label,page)

            return True

    def gettops(self):
        return rdfgettops()

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

        if isinstance(node, Unit):
            node = node.id

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

        page = getPageFromStore(node)
        if page:
            self.response.out.write( page )
            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.")
            PageStore.put(node,thing_tree)
            return True
        return False

    def checkConneg(self,node):
        accept_header = self.request.headers.get('Accept')
        if accept_header:
            accept_header = accept_header.split(',')
        else:
            accept_header = ""
        target = None
        for ah in accept_header:
            if target:
                break
            ah = re.sub( r";q=\d?\.\d+", '', ah).rstrip()
            log.debug("ACCEPT %s" % ah)
            if ah == "text/html":
                return False
            elif ah == "application/ld+json":
                target = ".jsonld"
            elif ah == "application/x-turtle":
                target = ".ttl"
            elif ah == "application/rdf+xml":
                target = ".rdf"
            elif ah == "text/plain":
                target = ".nt"
            elif ah == "text/csv":
                target = ".csv"
        if target:
            self.response.set_status(303,"See Other")
            self.response.headers['Location'] = makeUrl("","%s%s" % (node,target))
            self.emitCacheHeaders()
            return True
        return False

    def handleExactTermPage(self, node, layers='core'):

        baseuri = SdoConfig.baseUri()

        if node.startswith(baseuri): #Special case will map full schema URI to the term name
            node = node[len(baseuri):]

        """Handle with requests for specific terms like /Person, /fooBar. """
        dataext = os.path.splitext(node)
        if dataext[1] in OUTPUTDATATYPES:
            ret = self.handleExactTermDataOutput(dataext[0],dataext[1])
            if ret == True:
                return True
        if self.checkConneg(node):
            return True
        log.info("GETTING TERM: %s" % node)    
        term = VTerm.getTerm(node)

        if not term:
            return False
        
        if not self.checkNodeExt(term):
            return False
        
        if term.inLayers(layers):
            self.emitExactTermPage(term, layers=layers)
            return True

    def checkNodeExt(self,term):
        if os.environ.get('STAYINEXTENTION',"False").lower() == "true":
            return True

        home = term.getLayer()
        ext = getHostExt()
        log.info("term: '%s' home: '%s' ext: '%s'" % (term,home,ext))
        log.info("Supers: %s" % term.getSupers())
        if home == CORE and ext == '':
            return True
        if home == ext:
            return True

        if home == CORE:
            log.info("Redirecting to core entity")
            self.redirectToBase(term.getId(),full=True)
        else:
            log.info("Redirecting to '%s' entity" % home)
            self.redirectToExt(term.getId(),ext=home, full=True)
        return False


    def handleExactTermDataOutput(self, node=None, outputtype=None):
        log.info("handleExactTermDataOutput Node: '%s'  Outputtype: '%s'" % (node, outputtype))
        ret = False
        file = None
        if node and outputtype:
            term = VTerm.getTerm(node)
            if term:
                ret = True
                index = "%s:%s" % (outputtype,node)
                data = getPageFromStore(index)

                excludeAttic=True
                if getHostExt()== ATTIC:
                    excludeAttic=False
                if outputtype == ".csv":
                    self.response.headers['Content-Type'] = "text/csv; charset=utf-8"
                    if not data:
                        data = self.emitcsvTerm(term,excludeAttic)
                        PageStore.put(index,data)
                else:
                    format = None
                    if outputtype == ".jsonld":
                        self.response.headers['Content-Type'] = "application/ld+json; charset=utf-8"
                        format = "json-ld"
                    elif outputtype == ".ttl":
                        self.response.headers['Content-Type'] = "application/x-turtle; charset=utf-8"
                        format = "turtle"
                    elif outputtype == ".rdf" or outputtype == ".xml" :
                        self.response.headers['Content-Type'] = "application/rdf+xml; charset=utf-8"
                        format = "pretty-xml"
                    elif outputtype == ".nt":
                        self.response.headers['Content-Type'] = "text/plain; charset=utf-8"
                        format = "nt"

                    if format:
                        if not data:
                            data = serializeSingleTermGrapth(node=node, format=format, excludeAttic=True)
                            PageStore.put(index,data)
                if data:
                    self.emitCacheHeaders()
                    self.response.out.write( data )
                    ret = True
        return ret

    def emitcsvTerm(self,term,excludeAttic=True):
        csv = sdordf2csv(queryGraph=getQueryGraph(),fullGraph=getQueryGraph(),markdownComments=True,excludeAttic=excludeAttic)
        file = StringIO.StringIO()
        termUri = term.getUri()
        if term.isClass() or term.isEnumerationValue():
            csv.type2CSV(header=True,out=file)
            csv.type2CSV(term=termUri,header=False,out=file)
        elif term.isProperty():
            csv.prop2CSV(header=True,out=file)
            csv.prop2CSV(term=termUri,header=False,out=file)
        data = file.getvalue()
        file.close()
        return data


    def handle404Failure(self, node, layers="core", extrainfo=None, suggest=True):
        self.error(404)
        self.emitSchemaorgHeaders("404 Not Found")
        #404 could be called from any path, so output all potential locations of schemaorg.css
        self.response.out.write('<link rel="stylesheet" type="text/css" href="../docs/schemaorg.css" />')
        self.response.out.write('<link rel="stylesheet" type="text/css" href="docs/schemaorg.css" />')
        self.response.out.write('<link rel="stylesheet" type="text/css" href="/docs/schemaorg.css" />')

        self.response.out.write('<h3>404 Not Found.</h3><p><br/>Page not found. Please <a href="/">try the homepage.</a><br/><br/></p>')

        if suggest:
            clean_node = cleanPath(node)

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

            base_term = VTerm.getTerm( 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.getId(), base_term.getId() ))

            base_actionprop = VTerm.getTerm( 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.getId(), base_actionprop.getId() ))

        if extrainfo:
            self.response.out.write("<div>%s</div>" % extrainfo)

        self.response.out.write("</div>\n</body>\n<!--AppEngineVersion %s -->\n</html>\n"  % getAppEngineVersion())

        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 getPageFromStore('tocVersionPage'):
                self.response.out.write( getPageFromStore('tocVersionPage'))
                return True
            else:
                log.debug("Serving tocversionPage from cache.")
                page = templateRender('tocVersionPage.tpl', node,
                        {"releases": sorted(releaselog.iterkeys()),
                         "menu_sel": "Schemas"})

                self.response.out.write( page )
                log.debug("Serving fresh tocVersionPage.")
                PageStore.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 != "build-latest":
                return False
                log.info("giving up to 404.")
            else:
                log.info("generating a live view of this latest release.")


        if getPageFromStore('FullReleasePage'):
            self.response.out.write( getPageFromStore('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 = {}


            # 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_" )

                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()

            if requested_version == "build-latest":
                requested_version = SCHEMA_VERSION
                releasedate = "XXXX-XX-XX    (UNRELEASED PREVIEW VERSION)"
            else:
                releasedate = releaselog[str(SCHEMA_VERSION)]

            page = templateRender('fullReleasePage.tpl', node,
                    {"base_href": base_href,
                    'thing_tree': thing_tree,
                    'liveversion': SCHEMA_VERSION,
                    'requested_version': requested_version,
                    'releasedate': releasedate,
                    '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.")
            PageStore.put("FullReleasePage",page)
            return True

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

#        if getPageFromStore('ExtensionContents',ext):
#            return getPageFromStore('ExtensionContents',ext)

        buff = StringIO.StringIO()
        
        az_terms = VTerm.getAllTerms(layer=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 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()
#        PageStore.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():
                ret += 1
            elif select == "prop" and t.isProperty():
                ret += 1
            elif select == "enum" and t.isEnumerationValue():
                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()
                elif select == "prop":
                    use = t.isProperty()
                elif select == "enum":
                    use = t.isEnumerationValue()
                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=""):
        global noindexpages
        node = str(node)
        hostString = test
        host_ext = ""
        args = []
        if test == "":
            hostString = self.request.host
            args = self.request.arguments()

        ver=None
        if not getInTestHarness():
            from google.appengine.api.modules.modules import get_current_version_name
            ver = get_current_version_name()
        if hostString.startswith("%s." % ver):
            log.info("Removing version prefix '%s' from hoststring" % ver)
            hostString = hostString[len(ver) + 1:]

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

        match = re.match( r'([\w\-_]+)[\.:]?', hostString)
        host_ext = str(match.group(1))
        match0 = str(match.group(0))
        if host_ext + ":" == match0: #Special case for URLs with no subdomains - eg. localhost
            host_ext = ""

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


        log.info("setupHostinfo: data: scheme='%s' hoststring='%s' initial host_ext='%s'" % (scheme, hostString, str(host_ext) ))

        ver=None
        if not getInTestHarness():
            from google.appengine.api.modules.modules import get_current_version_name
            ver = get_current_version_name()

        if host_ext != "":
            if host_ext in ENABLED_EXTENSIONS:
                mybasehost = mybasehost[len(host_ext) + 1:]

            elif host_ext == "www":
                mybasehost = mybasehost[4:]
                setBaseHost(mybasehost)
                log.info("Host extention '%s' - redirecting to '%s'" % (host_ext,mybasehost))
                return self.redirectToBase(node,True)
            else:
                tempbase = mybasehost[len(host_ext)+1:]
                if tempbase in WORKINGHOSTS: #Known hosts so can control extention values
                    mybasehost = tempbase
                    setHostExt("")
                    setBaseHost(mybasehost)
                    log.info("Host extention '%s' not enabled - redirecting to '%s'" % (host_ext,mybasehost))
                    return self.redirectToBase(node,True)

                else:                        #Unknown host so host_ext may be just part of the host string
                    host_ext = ""

        log.info("setupHostinfo: calculated: basehost='%s' host_ext='%s'" % (mybasehost, host_ext ))

        setHostExt(host_ext)
        setBaseHost(mybasehost)

        if mybasehost == "schema.org":
            noindexpages = False
        if "FORCEINDEXPAGES" in os.environ:
            if os.environ["FORCEINDEXPAGES"] == "True":
                noindexpages = False
        log.info("[%s] noindexpages: %s" % (getInstanceId(short=True),noindexpages))

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

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

        dcn = "single" #Forcing single cache
        #log.info("Forcing single cache.  !!!!!!!!!!!!!!!!")
        #log.info("sdoapp.py setting current datacache to: %s " % dcn)
        DataCache.setCurrent(dcn)
        PageStore.setCurrent(dcn)
        HeaderStore.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="",full=False):
        uri = makeUrl("",node,full)
        log.info("Redirecting [301] to: %s" % uri)
        if not getInTestHarness():
            self.response = webapp2.redirect(uri, True, 301)
        return False

    def redirectToExt(self,node="",ext="",full=False):
        uri = makeUrl(ext,node,full)
        log.info("Redirecting [301] to: %s" % uri)
        if not getInTestHarness():
            self.response = webapp2.redirect(uri, True, 301)
        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
        log.info("NODE: '%s'" % node)

        if not node or node == "":
            node = "/"

        if not validNode_re.search(str(node)): #invalid node name
            log.warning("Invalid node name '%s'" % str(node))
            self.handle404Failure(node,suggest=False)
            return

        NotModified = False
        matchTag = self.request.headers.get("If-None-Match",None)
        unMod = self.request.headers.get("If-Unmodified-Since",None)

        #log.info("matchTag '%s' unMod '%s'" % (matchTag,unMod))

        hdrIndex = getHostExt()
        if len(hdrIndex):
            hdrIndex +=  ":"
        hdrIndex += node

        hdrs = HeaderStore.get(hdrIndex)

        if hdrs:
            etag = hdrs.get("ETag",None)
            mod = hdrs.get("Last-Modified",None)
            log.info("stored etag '%s' mod '%s'" % (etag,mod))

            if matchTag == etag:
                NotModified = True
            elif unMod:
                unModt = datetime.datetime.strptime(unMod,"%a, %d %b %Y %H:%M:%S %Z")
                modt = datetime.datetime.strptime(mod,"%a, %d %b %Y %H:%M:%S %Z")
                if modt <= unModt:
                    log.info("Last mod '%s' not modified since '%s' " % (mod,unMod))
                    NotModified = True

        if hdrs and "_pageFlush" in getArguments():
            log.info("Reloading header for %s" % hdrIndex)
            HeaderStore.remove(hdrIndex)
            hdrs = None
            NotModified = False

        if NotModified:
            self.response.clear()
            self.response.headers = hdrs
            self.response.set_status(304,"Not Modified")
        else:
            enableCaching = self._get(node) #Go get the page
            if enableCaching:
                if self.response.status.startswith("200"):
                    stat = getAppVar(CLOUDSTAT)
                    log.info("CLOUDSTAT %s" % stat)

                    if stat: #Use values from cloud storage
                        self.response.headers.add_header("ETag", stat.etag)
                        self.response.headers['Last-Modified'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",time.gmtime(stat.st_ctime))
                        self.response.headers['Content-Type'] = stat.content_type
                    else:
                        if not self.response.headers.get('Content-Type',None):                    
                            mimetype, contentType = mimetypes.guess_type(node)
                            self.response.headers['Content-Type'] = mimetype
                        
                        self.response.headers.add_header("ETag", getslug() + str(hash(hdrIndex)))
                        self.response.headers['Last-Modified'] = getmodiftime().strftime("%a, %d %b %Y %H:%M:%S GMT")

                    retHdrs = self.response.headers.copy()
                    HeaderStore.put(hdrIndex,retHdrs) #Cache these headers for a future 304 return

            #self.response.set_cookie('GOOGAPPUID', getAppEngineVersion())
        log.info("Responding: node: %s status: %s. headers: \n%s" % (node,self.response.status,self.response.headers ))


    def _get(self, node, doWarm=True):
        global LOADEDSOURCES
        """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

        Return True to enable browser caching ETag/Last-Modified - False for no cache
        """
        global_vars.time_start = datetime.datetime.now()
        tick() #keep system fresh

        log.info("[%s] _get(%s)" % (getInstanceId(short=True),node))

        self.callCount()

        if (node in silent_skip_list):
            return False

        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 not LOADEDSOURCES:
            log.info("Instance[%s] received request for not stored page: %s" % (getInstanceId(short=True), node) )
            log.info("Instance[%s] needs to load sources to create it" % (getInstanceId(short=True)) )
            load_sources() #Get Examples files and schema definitions

        if node.startswith("docs/"):
            return self._getDocs(node,layerlist=layerlist)

        if(node == "_ah/warmup"):
            if "localhost" in os.environ['SERVER_NAME'] and WarmupState.lower() == "auto":
                log.info("[%s] Warmup dissabled for localhost instance" % getInstanceId(short=True))
                if DISABLE_NDB_FOR_LOCALHOST:
                    log.info("[%s] NDB dissabled for localhost instance" % getInstanceId(short=True))
                    enablePageStore("INMEM")
            else:
                if not memcache.get("warmedup"):
                    memcache.set("warmedup", value=True)
                    self.warmup()
                else:
                    log.info("Warmup already actioned")
            return False
        #elif doWarm:  #Do a bit of warming on each call
            #global WarmedUp
            #global Warmer
            #if not WarmedUp:
                #Warmer.stepWarm(self)
                
        

        self.emitHTTPHeaders(node) #Ensure we have the right basic header values
        
        if(node == "admin/refresh"):
            log.info("Processing refesh request")
            load_start = datetime.datetime.now() 
            memcache.flush_all()        
            memcache.set(key="app_initialising", value=True, time=300)  #Give the system 5 mins - auto remove flag in case of crash
            cleanmsg = CacheControl.clean()
            log.info("Clean count(s): %s" % cleanmsg)
            log.info(("[%s] Cache clean took %s " % (getInstanceId(short=True),(datetime.datetime.now() - load_start))))
            memcache.set(key="app_initialising", value=False)
            storeInitialisedTimestamp()
            self.emitSchemaorgHeaders("Refresh")
            #404 could be called from any path, so output all potential locations of schemaorg.css
            self.response.out.write('<link rel="stylesheet" type="text/css" href="../docs/schemaorg.css" />')
            self.response.out.write('<link rel="stylesheet" type="text/css" href="docs/schemaorg.css" />')
            self.response.out.write('<link rel="stylesheet" type="text/css" href="/docs/schemaorg.css" />')

            self.response.out.write('<h3>Refresh Completed</h3><p>Took: %s</p>' % (datetime.datetime.now() - load_start))
            return False
            

        if(node == "_ah/start"):
            log.info("Instance[%s] received Start request at %s" % (modules.get_current_instance_id(), global_vars.time_start) )
            if "localhost" in os.environ['SERVER_NAME'] and WarmupState.lower() == "auto":
                log.info("[%s] Warmup dissabled for localhost instance" % getInstanceId(short=True))
                if DISABLE_NDB_FOR_LOCALHOST:
                    log.info("[%s] NDB dissabled for localhost instance" % getInstanceId(short=True))
                    enablePageStore("INMEM")
            else:
                if not memcache.get("warmedup"):
                    memcache.set("warmedup", value=True)
                    self.warmup()
                else:
                    log.info("Warmup already actioned")
            return False

        if(node == "_ah/stop"):
            log.info("Instance[%s] received Stop request at %s" % (modules.get_current_instance_id(), global_vars.time_start) )
            log.info("Flushing memcache")
            memcache.flush_all()
            return False

            ##########################################################################################################
            #VTerm.getTerm("EBook")
            #os.exit()
            ##########################################################################################################
            

        if (node in ["", "/"]):
            return self.handleHomepage(node)

        currentVerPath = "version/%s" % SCHEMA_VERSION
            
        if(node.startswith("version/latest")):
            newurl = "%s%s" % (currentVerPath,node[14:])
            log.info("REDIRECTING TO: %s" % newurl)
            self.response.set_status(302,"Found")
            self.response.headers['Location'] = makeUrl("",newurl)
            self.emitCacheHeaders()
            return False #don't cache this redirect

        #Match nodes of pattern 'version/*' 'version/*/' or 'version/'
        if (re.match(r'^version/[^/]*$', str(node)) or re.match(r'^version/[^/]*/$', str(node)) or node == "version/") :
            if self.handleFullReleasePage(node, layerlist=layerlist):
                return True
            else:
                log.info("Error handling full release page: %s " % node)
                if self.handle404Failure(node):
                    return False
                else:
                    log.info("Error handling 404 under /version/")
                    return False

        if(node == "_siteDebug"):
            if(getBaseHost() != "schema.org" or os.environ['PRODSITEDEBUG'] == "True"):
                self.siteDebug()
                return False #Treat as a dynamic page - suppress Etags etc.

        if(node == "_cacheFlush"):
            setmodiftime(datetime.datetime.utcnow()) #Resets etags and modtime
            counts = CacheControl.clean(pagesonly=True)
            inf = "<div style=\"clear: both; float: left; text-align: left; font-size: xx-small; color: #888 ; margin: 1em; line-height: 100%;\">"
            inf +=  str(counts)
            inf += "</div>"
            self.handle404Failure(node,extrainfo=inf)
            return False


        # Pages based on request path matching a Unit in the term graph:
        if self.handleExactTermPage(node, layers=layerlist):
            return True
        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 False
            else:
                log.info("Error handling 404.")
                return False

    def _getDocs(self, node, layerlist=""):
        hstext = getHostExt()
        if hstext == "":
            hstext = "core"
        
        if (node.startswith("docs/") and hstext != "core"): #All docs should operate in core
            return self.redirectToBase(node,True)

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

        elif (node == "docs/full.html"):
            if self.handleFullHierarchyPage(node, layerlist=layerlist):
                return True
            else:
                log.info("Error handling full.html : %s " % node)
                return False

        elif (node == "docs/schemas.html"):
            if self.handleSchemasPage(node, layerlist=layerlist):
                return True
            else:
                log.info("Error handling schemas.html : %s " % node)
                return False
        elif (node == "docs/developers.html"):
            if self.handleDumpsPage(node, layerlist=layerlist):
                return True
            else:
                log.info("Error handling developers.html : %s " % node)
                return False

        elif (node == "docs/tree.jsonld" or node == "docs/tree.json"):
            if self.handleJSONSchemaTree(node, layerlist=ALL_LAYERS):
                return True
            else:
                log.info("Error handling JSON-LD schema tree: %s " % node)
                return False
        else: #Asking for a sttic file under docs
            return self.handleStaticDoc(node)
            
    def handleStaticDoc(self,node):
        if PAGESTOREMODE == "CLOUDSTORE":
            log.info("Asking for: %s" % node)
            page = getPageFromStore(node,enableFlush=False)
            if page:
                self.response.out.write( page )
                log.debug("Serving static page: %s" % node)
                return True
            else:
                self.handle404Failure(node)
                return False
            
            
        return False
        
    def siteDebug(self):
        global STATS
        page = templateRender('siteDebug.tpl', "_siteDebug" )

        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<!--AppEngineVersion %s -->\n</html>\n"  % getAppEngineVersion())

    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

        warm_start = datetime.datetime.now()
        log.debug("Instance[%s] received Warmup request at %s" % (modules.get_current_instance_id(), datetime.datetime.utcnow()) )
        if memcache.get("Warming"):
            log.debug("Instance[%s] detected system already warming" % (modules.get_current_instance_id()) )
        else:
            memcache.set("Warming",True,time=300)
            Warmer.warmAll(self)
            log.debug("Instance[%s] completed Warmup request at %s elapsed: %s" % (modules.get_current_instance_id(), datetime.datetime.utcnow(),datetime.datetime.now() - warm_start ) )
            memcache.set("Warming",False)

class WarmupTool():

    def __init__(self):
        #self.pageList = ["docs/schemas.html"]
        self.pageList = ["/","docs/schemas.html","docs/full.html","docs/tree.jsonld","docs/developers.html","docs/jsonldcontext.json"]
        self.extPageList = ["/"] #Pages warmed in all extentions
        self.warmPages = {}
        for l in ALL_LAYERS:
            self.warmPages[l] = []
        self.warmedLayers = []

    def stepWarm(self, unit=None, layer=None):
        lock = threading.Lock()
        with lock:
            realHostExt = getHostExt()
            if layer:
                setHostExt(layer)

            self._stepWarm(unit=unit, layer=layer)

            setHostExt(realHostExt)


    def _stepWarm(self, unit=None, layer=None):
        global WarmedUp
        if not layer:
            layer = getHostExt()
            if layer == "":
                layer = "core"
        if not unit or WarmedUp:
            return
        if layer in self.warmedLayers: #Done all for this layer
            return
        warmedPages = False
        for p in self.pageList:
            if p not in self.warmPages[layer]:
                self.warmPages[layer].append(p)
                if layer == "core" or p in self.extPageList: #Only warm selected pages in extensions
                    log.info("Warming page %s in layer %s" % (p,layer))
                    unit._get(p,doWarm=False)
                    unit.response.clear()
                if len(self.warmPages[layer]) == len(self.pageList):
                    warmedPages = True
                break
        if warmedPages: #Must be all warmed for this layer
            log.info("All warmed in layer %s" % layer)
            self.warmedLayers.append(layer)
            self.checkAll()

    def checkAll(self):
        global WarmedUp
        allDone = True
        for l in ALL_LAYERS:
            if l != "" and l not in self.warmedLayers:
                allDone = False
                break
        if allDone:
            WarmedUp = True
            log.info("All layers warmed!")

    def warmAll(self,unit):
        global WarmedUp
        while not WarmedUp:
            for l in ALL_LAYERS:
                self.stepWarm(layer=l,unit=unit)

Warmer = WarmupTool()

def getExtenstionDescriptions():
    extComment = ""
    extVers = ""
    extName = ""
    extDD = ""
    ex = getHostExt()
    if ex and len(ex):
        descs = api.SdoConfig.descriptor(ex)
        if descs and len(descs):
            extName = descs[0].get("name")
            extDD = Markdown.parse(descs[0].get("brief"))
            extVers = Markdown.parse(descs[0].get("version"))
            extComment = Markdown.parse(descs[0].get("comment"))
        
    return extName, extDD, extVers, extComment

def templateRender(templateName, node, values=None):
    global sitemode #,sitename
    #log.info("templateRender(%s,%s,%s)" % (templateName, node, values))
    #log.info("getHostExt %s" % getHostExt())


    if isinstance(node, Unit):
        node = node.id
    if isinstance(node, VTerm):
        node = node.getId()

    extName, extDD, extVers, extComment =  getExtenstionDescriptions() 

    if node.startswith("docs/"):
        docsdir = "./"
        homedir = ".."
    else:
        docsdir = "docs/"
        homedir = "."
    defvars = {
        'ENABLE_HOSTED_EXTENSIONS': ENABLE_HOSTED_EXTENSIONS,
        'SCHEMA_VERSION': SCHEMA_VERSION,
        'appengineVersion': getAppEngineVersion(),
        'debugging': getAppVar('debugging'),
        'docsdir': docsdir,
        'extComment': extComment,
        'extDD': extDD,
        'extName': extName,
        'extVers': extVers,
        'extensionPath': makeUrl(getHostExt(),"",full=True),
        'homedir': homedir,
        'host_ext': getHostExt(),
        'mybasehost': getBaseHost(),
        'myhost': getHost(),
        'myport': getHostPort(),
        'sitemode': sitemode,
        'sitename': SdoConfig.getname(),
        'staticPath': homedir,
        'targethost': makeUrl("","",full=True),
        'vocabUri': SdoConfig.vocabUri()
    }

    if values:
        defvars.update(values)
    template = JINJA_ENVIRONMENT.get_template(templateName)
    return template.render(defvars)
    
def oldtemplateRender(templateName, node, values=None):
    global sitemode #,sitename
    log.info("templateRender(%s,%s,%s)" % (templateName, node, values))
    log.info("getHostExt %s" % getHostExt())


    if isinstance(node, Unit):
        node = node.id

    extDef = Unit.GetUnit(getNss(getHostExt()),True)
    extComment = ""
    extVers = ""
    extName = ""
    #log.info("EXDEF '%s'" % extDef)
    if extDef:
        extComment = GetComment(extDef,ALL_LAYERS)
        if extComment == "-":
            extComment = ""
        extDDs = GetTargets(Unit.GetUnit("schema:disambiguatingDescription", True), extDef, layers=ALL_LAYERS )
        if len(extDDs) > 0:
            extDD = Markdown.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 += Markdown.parse(ver)
        if len(extVers) :
            extVers += ")</em>"
        nms = GetTargets(Unit.GetUnit("schema:name", True), extDef, layers=ALL_LAYERS )
        if len(nms) > 0:
            extName = nms[0]
    if node.startswith("docs/"):
        docsdir = "./"
        homedir = ".."
    else:
        docsdir = "docs/"
        homedir = "."
    defvars = {
        'ENABLE_HOSTED_EXTENSIONS': ENABLE_HOSTED_EXTENSIONS,
        'SCHEMA_VERSION': SCHEMA_VERSION,
        'sitemode': sitemode,
        'sitename': SdoConfig.getname(),
        'staticPath': homedir,
        'extensionPath': makeUrl(getHostExt(),"",full=True),
        'myhost': getHost(),
        'myport': getHostPort(),
        'mybasehost': getBaseHost(),
        'host_ext': getHostExt(),
        'extComment': extComment,
        'docsdir': docsdir,
        'homedir': homedir,
        'extDD': extDD,
        'extVers': extVers,
        'extName': extName,
        'targethost': makeUrl("","",full=True),
        'debugging': getAppVar('debugging'),
        'appengineVersion': getAppEngineVersion()
    }

    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")
        if Insts:
            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)

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 setArguments(val):
    setAppVar('myarguments',val)

def getArguments():
    return getAppVar('myarguments')

def makeUrl(ext="",path="",full=False,scheme=None):
        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

        if full:
            if not scheme:
                scheme = getHttpScheme()

            targethost = os.environ.get("TARGETSITE",getBaseHost())

            url = "%s://%s%s%s%s" % (scheme,sub,targethost,port,p)
        else:
            url = "%s" % (p)
        return url

def getPageFromStore(id,ext=None,enableFlush=True):
        cached = PageStore.get(id,ext)
        if enableFlush and cached and "_pageFlush" in getArguments():
            log.info("Reloading page for %s" % id)
            PageStore.remove(id,ext)
            cached = None
        return cached

schemasInitialized = False
def load_schema_definitions():
    log.info("STARTING UP... reading schemas.")
    #load_graph(loadExtensions=ENABLE_HOSTED_EXTENSIONS)
    global schemasInitialized
    if SdoConfig.isValid():
       read_schemas(SdoConfig.termFiles())
       load_usage_data(SdoConfig.countsFiles())
    else:
        read_local_schemas(loadExtensions=ENABLE_HOSTED_EXTENSIONS)
        if ENABLE_HOSTED_EXTENSIONS:
            read_extensions(ENABLED_EXTENSIONS)
    schemasInitialized = True

LOADINGSOURCE = None
WAITSECS = 360
def load_sources():
    global LOADINGSOURCE, LOADEDSOURCES,WAITSECS
    if LOADEDSOURCES:
        return
    if LOADINGSOURCE: #Another thread may already be here
        elapsedSecs = 0
        while LOADINGSOURCE and elapsedSecs < WAITSECS:
            time.sleep(0.1)
            if LOADINGSOURCE: #If still loading, check timing and go around again
                elapsed = datetime.datetime.now() - LOADINGSOURCE
                elapsedSecs = elapsed.total_seconds()

        if elapsedSecs >= WAITSECS: # Clear potential thread block caused by another thread crashing out leaving flags set
            log.info("LOADINGSOURCE Thread blocked for over %s seconds - clearing lock" % WAITSECS)
            LOADINGSOURCE = None

    if not LOADEDSOURCES and not LOADINGSOURCE: # Check again in case things have changed in above loop
        LOADINGSOURCE = datetime.datetime.now()
        load_start = datetime.datetime.now()
        load_schema_definitions()
        log.info(("[%s] Term definitions load took %s " % (getInstanceId(short=True),(datetime.datetime.now() - load_start))))
        load_start = datetime.datetime.now()
        load_examples_data(ENABLED_EXTENSIONS)
        log.info(("[%s] Examples load took %s " % (getInstanceId(short=True),(datetime.datetime.now() - load_start))))
        LOADEDSOURCES=True
        LOADINGSOURCE=None

if getInTestHarness():
    load_sources()
else:
    app = ndb.toplevel(webapp2.WSGIApplication([("/(.*)", ShowUnit)]))
back to top