def poll(self, *args):
        namespace, docid = self.docselector(*args)

        if 'X-Sessionid' in cherrypy.request.headers:
            sid = cherrypy.request.headers['X-Sessionid']
        else:
            raise cherrypy.HTTPError(404, "Expected X-Sessionid " + namespace + "/" + docid)

        #set last access
        log("Poll from session " + sid + " for " + "/".join((namespace,docid)))
        self.docstore.lastaccess[(namespace,docid)][sid] = time.time()

        if namespace == "testflat":
            return "{\"version\":\""+VERSION+"\"}" #no polling for testflat

        self.checkexpireconcurrency()

        if sid in self.docstore.updateq[(namespace,docid)]:
            ids = self.docstore.updateq[(namespace,docid)][sid]
            self.docstore.updateq[(namespace,docid)][sid] = set() #reset
            if ids:
                cherrypy.log("Successful poll from session " + sid + " for " + "/".join((namespace,docid)) + ", returning IDs: " + " ".join(ids))
                doc = self.docstore[(namespace,docid)]
                results = [[ doc[id] for id in ids if id in doc ]] #results are grouped by query, but we lose that distinction here and group them all in one, hence the double list
                return parseresults(results, doc, **{'version': VERSION, 'sid':sid, 'lastaccess': self.docstore.lastaccess[(namespace,docid)]})
            else:
                return json.dumps({'sessions': len([s for s in self.docstore.lastaccess[(namespace,docid)] if s != 'NOSID' ])}).encode('utf-8')
        else:
            return json.dumps({'sessions': len([s for s in self.docstore.lastaccess[(namespace,docid)] if s != 'NOSID' ])}).encode('utf-8')
    def poll(self, *args):
        namespace, docid = self.docselector(*args)

        if 'X-sessionid' in cherrypy.request.headers:
            sid = cherrypy.request.headers['X-sessionid']
        else:
            raise cherrypy.HTTPError(404, "Expected X-sessionid " + namespace + "/" + docid)

        if namespace == "testflat":
            return "{}" #no polling for testflat

        self.checkexpireconcurrency()

        if sid in self.docstore.updateq[(namespace,docid)]:
            ids = self.docstore.updateq[(namespace,docid)][sid]
            self.docstore.updateq[(namespace,docid)][sid] = set() #reset
            if ids:
                cherrypy.log("Succesful poll from session " + sid + " for " + "/".join((namespace,docid)) + ", returning IDs: " + " ".join(ids))
                doc = self.docstore[(namespace,docid)]
                results = [ doc[id] for id in ids if id in doc ]
                return parseresults(results, doc, **{'sid':sid, 'lastaccess': self.docstore.lastaccess[(namespace,docid)]})
            else:
                return json.dumps({'sessions': len([s for s in self.docstore.lastaccess[(namespace,docid)] if s != 'NOSID' ])}).encode('utf-8')
        else:
            return json.dumps({'sessions': len([s for s in self.docstore.lastaccess[(namespace,docid)] if s != 'NOSID' ])}).encode('utf-8')
    def query(self, **kwargs):
        """Query method, all FQL queries arrive here"""

        if 'X-Sessionid' in cherrypy.request.headers:
            sid = cherrypy.request.headers['X-Sessionid']
        else:
            sid = 'NOSID'

        if 'query' in kwargs:
            rawqueries = kwargs['query'].split("\n")
        else:
            cl = cherrypy.request.headers['Content-Length']
            rawqueries = cherrypy.request.body.read(int(cl)).split("\n")

        if self.debug:
            for i,rawquery in enumerate(rawqueries):
                log("[QUERY INCOMING #" + str(i+1) + ", SID=" +sid + "] " + rawquery)

        #Get parameters for FLAT-specific return format
        flatargs = getflatargs(cherrypy.request.params)
        flatargs['debug'] = self.debug
        flatargs['logfunction'] = log
        flatargs['version'] = VERSION

        prevdocsel = None
        sessiondocsel = None
        queries = []
        metachanges = {}
        for rawquery in rawqueries:
            try:
                docsel, rawquery = getdocumentselector(rawquery)
                if not docsel: docsel = prevdocsel
                self.docstore.use(docsel)
                if self.debug >= 2: log("[acquired lock " + "/".join(docsel)+"]")
                if not sessiondocsel: sessiondocsel = docsel
                if rawquery == "GET":
                    query = "GET"
                elif rawquery == "PROBE":
                    query = "PROBE" #gets no content data at all, but allows returning associated metadata used by FLAT, forces FLAT format
                else:
                    if rawquery[:4] == "CQL ":
                        if rawquery.find('FORMAT') != -1:
                            end = rawquery.find('FORMAT')
                            format = rawquery[end+7:]
                        else:
                            end = 9999
                            format = 'xml'
                        try:
                            query = fql.Query(cql.cql2fql(rawquery[4:end]))
                            query.format = format
                        except cql.SyntaxError as e :
                            raise fql.SyntaxError("Error in CQL query: " + str(e))
                    elif rawquery[:5] == "META ":
                        try:
                            key, value = rawquery[5:].split('=',maxsplit=1)
                        except ValueError:
                            raise fql.SyntaxError("Expected key=value after META keyword")
                        key = key.strip()
                        value = value.strip()
                        metachanges[key] = value
                        query = None
                    else:
                        query = fql.Query(rawquery)
                    if query and query.format == "python":
                        query.format = "xml"
                    if query and query.action and not docsel:
                        raise fql.SyntaxError("Document Server requires USE statement prior to FQL query")
            except fql.SyntaxError as e:
                log("[QUERY ON " + "/".join(docsel)  + "] " + str(rawquery))
                log("[QUERY FAILED] FQL Syntax Error: " + str(e))
                raise cherrypy.HTTPError(404, "FQL syntax error: " + str(e))
            finally:
                if self.debug >= 2: log("[releasing lock " + "/".join(docsel))
                self.docstore.done(docsel)

            if query:
                queries.append( (query, rawquery))
            prevdocsel = docsel


        if metachanges:
            try:
                doc = self.docstore[docsel]
            except NoSuchDocument:
                log("[QUERY FAILED] No such document")
                raise cherrypy.HTTPError(404, "Document not found: " + docsel[0] + "/" + docsel[1])
            except Exception as e:
                exc_type, exc_value, exc_traceback = sys.exc_info()
                traceback.print_tb(exc_traceback, limit=50, file=sys.stderr)
                print("[QUERY FAILED] FoLiA Error in " + "/".join(docsel) + ": [" + e.__class__.__name__ + "] " + str(e), file=sys.stderr)
                log("[QUERY FAILED] FoLiA Error in " + "/".join(docsel) + ": [" + e.__class__.__name__ + "] " + str(e))
                if logfile: traceback.print_tb(exc_traceback, limit=50, file=logfile)
                raise cherrypy.HTTPError(404, "FoLiA error in " + "/".join(docsel) + ": [" + e.__class__.__name__ + "] " + str(e) + "\n\nQuery was: " + rawquery)

            if doc.metadatatype == folia.MetaDataType.NATIVE:
                doc.changed = True
                self.docstore.lastaccess[docsel][sid] = time.time()
                log("[METADATA EDIT ON " + "/".join(docsel)  + "]")
                for key, value in metachanges.items():
                    if value == 'NONE':
                        del doc.metadata[key]
                    else:
                        doc.metadata[key] = value
            else:
                raise cherrypy.HTTPError(404, "Unable to edit metadata on document with non-native metadata type (" + "/".join(docsel)+")")
        else:
            doc = None #initialize document only if not already initialized by metadta changes


        results = [] #stores all results
        xresults = [] #stores results that should be transferred to other sessions as well, i.e. results of adds/edits
        prevdocid = None
        multidoc = False #are the queries over multiple distinct documents?
        format = None
        for query, rawquery in queries:
            try:
                doc = self.docstore[docsel]
                self.docstore.lastaccess[docsel][sid] = time.time()
                log("[QUERY ON " + "/".join(docsel)  + "] " + str(rawquery))
                if isinstance(query, fql.Query):
                    if prevdocid and doc.id != prevdocid:
                        multidoc = True
                    result =  query(doc,False,self.debug >= 2)
                    results.append(result) #False = nowrap
                    if query.action and query.action.action in ('EDIT','ADD','DELETE', 'SUBSTITUTE','PREPEND','APPEND'):
                        #results of edits should be transferred to other open sessions
                        xresults.append(result)
                    if self.debug:
                        log("[QUERY RESULT] " + repr(result))
                    format = query.format
                    if query.action and query.action.action != "SELECT":
                        doc.changed = True
                        self.addtochangelog(doc, query, docsel)
                elif query == "GET":
                    results.append(doc.xmlstring())
                    format = "single-xml"
                elif query == "PROBE":
                    #no queries to perform
                    format = "flat"
                else:
                    raise Exception("Invalid query")
            except NoSuchDocument:
                if self.docstore.fail and not self.docstore.ignorefail:
                    log("[QUERY FAILED] Document server is in lockdown due to earlier failure. Restart required!")
                    raise cherrypy.HTTPError(403, "Document server is in lockdown due to earlier failure. Contact your FLAT administrator")
                else:
                    log("[QUERY FAILED] No such document")
                    raise cherrypy.HTTPError(404, "Document not found: " + docsel[0] + "/" + docsel[1])
            except fql.QueryError as e:
                log("[QUERY FAILED] FQL Query Error: " + str(e))
                raise cherrypy.HTTPError(404, "FQL query error: " + str(e))
            except Exception as e:
                exc_type, exc_value, exc_traceback = sys.exc_info()
                traceback.print_tb(exc_traceback, limit=50, file=sys.stderr)
                log("[QUERY FAILED] FoLiA Error in " + "/".join(docsel) + ": [" + e.__class__.__name__ + "] " + str(e))
                print("[QUERY FAILED] FoLiA Error in " + "/".join(docsel) + ": [" + e.__class__.__name__ + "] " + str(e), file=sys.stderr)
                if logfile: traceback.print_tb(exc_traceback, limit=50, file=logfile)
                raise cherrypy.HTTPError(404, "FoLiA error in " + "/".join(docsel) + ": [" + e.__class__.__name__ + "] " + str(e) + "\n\nQuery was: " + rawquery)
            prevdocid = doc.id

        if not format:
            if metachanges:
                return "{\"version\":\"" + VERSION + "\"}"
            else:
                raise cherrypy.HTTPError(404, "No queries given")
        if format.endswith('xml'):
            cherrypy.response.headers['Content-Type']= 'text/xml'
        elif format.endswith('json'):
            cherrypy.response.headers['Content-Type']= 'application/json'


        if format == "xml":
            out = "<results>" + "\n".join(results) + "</results>"
        elif format == "json":
            out = "[" + ",".join(results) + "]"
        elif format == "flat":
            if sid != 'NOSID' and sessiondocsel:
                self.setsession(sessiondocsel[0],sessiondocsel[1],sid, xresults)
            cherrypy.response.headers['Content-Type']= 'application/json'
            if multidoc:
                raise "{\"version\":\""+VERSION +"\"} //multidoc response, not producing results"
            elif doc:
                log("[Parsing results for FLAT]")
                out =  parseresults(results, doc, **flatargs)
        else:
            if len(results) > 1:
                raise cherrypy.HTTPError(404, "Multiple results were obtained but format dictates only one can be returned!")
            out = results[0]


        if docsel[0] == "testflat":
            testresult = self.docstore.save(docsel) #won't save, will run tests instead
            log("Test result: " +str(repr(testresult)))


            if format == "flat":
                out = json.loads(str(out,'utf-8'))
                out['testresult'] = testresult[0]
                out['testmessage'] = testresult[1]
                out['queries'] = rawqueries
                out = json.dumps(out)

            #unload the document, we want a fresh copy every time
            del self.docstore.data[('testflat','testflat')]

        if self.debug:
            if isinstance(out,bytes):
                log("[FINAL RESULTS] " + str(out,'utf-8'))
            else:
                log("[FINAL RESULTS] " + out)

        if isinstance(out,str):
            return out.encode('utf-8')
        else:
            return out
    def query(self, **kwargs):
        """Query method, all FQL queries arrive here"""

        if 'X-sessionid' in cherrypy.request.headers:
            sid = cherrypy.request.headers['X-sessionid']
        else:
            sid = 'NOSID'

        if 'query' in kwargs:
            rawqueries = kwargs['query'].split("\n")
        else:
            cl = cherrypy.request.headers['Content-Length']
            rawqueries = cherrypy.request.body.read(int(cl)).split("\n")

        if self.debug:
            for i,rawquery in enumerate(rawqueries):
                log("[QUERY INCOMING #" + str(i+1) + "] " + rawquery)

        #Get parameters for FLAT-specific return format
        flatargs = getflatargs(cherrypy.request.params)
        flatargs['debug'] = self.debug
        flatargs['logfunction'] = log

        prevdocsel = None
        sessiondocsel = None
        queries = []
        metachanges = {}
        for rawquery in rawqueries:
            try:
                docsel, rawquery = getdocumentselector(rawquery)
                if not docsel: docsel = prevdocsel
                self.docstore.use(docsel)
                if self.debug >= 2: log("[acquired lock " + "/".join(docsel)+"]")
                if not sessiondocsel: sessiondocsel = docsel
                if rawquery == "GET":
                    query = "GET"
                elif rawquery == "PROBE":
                    query = "PROBE" #gets no content data at all, but allows returning associated metadata used by FLAT, forces FLAT format
                else:
                    if rawquery[:4] == "CQL ":
                        if rawquery.find('FORMAT') != -1:
                            end = rawquery.find('FORMAT')
                            format = rawquery[end+7:]
                        else:
                            end = 9999
                            format = 'xml'
                        try:
                            query = fql.Query(cql.cql2fql(rawquery[4:end]))
                            query.format = format
                        except cql.SyntaxError as e :
                            raise fql.SyntaxError("Error in CQL query: " + str(e))
                    elif rawquery[:5] == "META ":
                        try:
                            key, value = rawquery[5:].split('=',maxsplit=1)
                        except ValueError:
                            raise fql.SyntaxError("Expected key=value after META keyword")
                        key = key.strip()
                        value = value.strip()
                        metachanges[key] = value
                        query = None
                    else:
                        query = fql.Query(rawquery)
                    if query and query.format == "python":
                        query.format = "xml"
                    if query and query.action and not docsel:
                        raise fql.SyntaxError("Document Server requires USE statement prior to FQL query")
            except fql.SyntaxError as e:
                log("[QUERY ON " + "/".join(docsel)  + "] " + str(rawquery))
                log("[QUERY FAILED] FQL Syntax Error: " + str(e))
                raise cherrypy.HTTPError(404, "FQL syntax error: " + str(e))
            finally:
                if self.debug >= 2: log("[releasing lock " + "/".join(docsel))
                self.docstore.done(docsel)

            if query:
                queries.append( (query, rawquery))
            prevdocsel = docsel


        if metachanges:
            try:
                doc = self.docstore[docsel]
            except NoSuchDocument:
                log("[QUERY FAILED] No such document")
                raise cherrypy.HTTPError(404, "Document not found: " + docsel[0] + "/" + docsel[1])
            except Exception as e:
                exc_type, exc_value, exc_traceback = sys.exc_info()
                traceback.print_tb(exc_traceback, limit=50, file=sys.stderr)
                print("[QUERY FAILED] FoLiA Error in " + "/".join(docsel) + ": [" + e.__class__.__name__ + "] " + str(e), file=sys.stderr)
                log("[QUERY FAILED] FoLiA Error in " + "/".join(docsel) + ": [" + e.__class__.__name__ + "] " + str(e))
                if logfile: traceback.print_tb(exc_traceback, limit=50, file=logfile)
                raise cherrypy.HTTPError(404, "FoLiA error in " + "/".join(docsel) + ": [" + e.__class__.__name__ + "] " + str(e) + "\n\nQuery was: " + rawquery)

            if doc.metadatatype == folia.MetaDataType.NATIVE:
                self.docstore.lastaccess[docsel][sid] = time.time()
                log("[METADATA EDIT ON " + "/".join(docsel)  + "]")
                for key, value in metachanges.items():
                    if value == 'NONE':
                        del doc.metadata[key]
                    else:
                        doc.metadata[key] = value
            else:
                raise cherrypy.HTTPError(404, "Unable to edit metadata on document with non-native metadata type (" + "/".join(docsel)+")")


        results = []
        doc = None
        prevdocid = None
        multidoc = False #are the queries over multiple distinct documents?
        format = None
        for query, rawquery in queries:
            try:
                doc = self.docstore[docsel]
                self.docstore.lastaccess[docsel][sid] = time.time()
                log("[QUERY ON " + "/".join(docsel)  + "] " + str(rawquery))
                if isinstance(query, fql.Query):
                    if prevdocid and doc.id != prevdocid:
                        multidoc = True
                    result =  query(doc,False,self.debug >= 2)
                    results.append(result) #False = nowrap
                    if self.debug:
                        log("[QUERY RESULT] " + repr(result))
                    format = query.format
                    if query.action and query.action.action != "SELECT":
                        doc.changed = True
                        self.addtochangelog(doc, query, docsel)
                elif query == "GET":
                    results.append(doc.xmlstring())
                    format = "single-xml"
                elif query == "PROBE":
                    #no queries to perform
                    format = "flat"
                else:
                    raise Exception("Invalid query")
            except NoSuchDocument:
                log("[QUERY FAILED] No such document")
                raise cherrypy.HTTPError(404, "Document not found: " + docsel[0] + "/" + docsel[1])
            except fql.QueryError as e:
                log("[QUERY FAILED] FQL Query Error: " + str(e))
                raise cherrypy.HTTPError(404, "FQL query error: " + str(e))
            except Exception as e:
                exc_type, exc_value, exc_traceback = sys.exc_info()
                traceback.print_tb(exc_traceback, limit=50, file=sys.stderr)
                log("[QUERY FAILED] FoLiA Error in " + "/".join(docsel) + ": [" + e.__class__.__name__ + "] " + str(e))
                print("[QUERY FAILED] FoLiA Error in " + "/".join(docsel) + ": [" + e.__class__.__name__ + "] " + str(e), file=sys.stderr)
                if logfile: traceback.print_tb(exc_traceback, limit=50, file=logfile)
                raise cherrypy.HTTPError(404, "FoLiA error in " + "/".join(docsel) + ": [" + e.__class__.__name__ + "] " + str(e) + "\n\nQuery was: " + rawquery)
            prevdocid = doc.id

        if not format:
            if metachanges:
                return "{}"
            else:
                raise cherrypy.HTTPError(404, "No queries given")
        if format.endswith('xml'):
            cherrypy.response.headers['Content-Type']= 'text/xml'
        elif format.endswith('json'):
            cherrypy.response.headers['Content-Type']= 'application/json'


        if format == "xml":
            out = "<results>" + "\n".join(results) + "</results>"
        elif format == "json":
            out = "[" + ",".join(results) + "]"
        elif format == "flat":
            if sid != 'NOSID' and sessiondocsel and not multidoc:
                self.createsession(sessiondocsel[0],sessiondocsel[1],sid, results)
            cherrypy.response.headers['Content-Type']= 'application/json'
            if multidoc:
                raise "{} //multidoc response, not producing results"
            elif doc:
                log("[Parsing results for FLAT]")
                out =  parseresults(results, doc, **flatargs)
        else:
            if len(results) > 1:
                raise cherrypy.HTTPError(404, "Multiple results were obtained but format dictates only one can be returned!")
            out = results[0]


        if docsel[0] == "testflat":
            testresult = self.docstore.save(docsel) #won't save, will run tests instead
            log("Test result: " +str(repr(testresult)))


            if format == "flat":
                out = json.loads(str(out,'utf-8'))
                out['testresult'] = testresult[0]
                out['testmessage'] = testresult[1]
                out['queries'] = rawqueries
                out = json.dumps(out)

            #unload the document, we want a fresh copy every time
            del self.docstore.data[('testflat','testflat')]

        if self.debug:
            if isinstance(out,bytes):
                log("[FINAL RESULTS] " + str(out,'utf-8'))
            else:
                log("[FINAL RESULTS] " + out)

        if isinstance(out,str):
            return out.encode('utf-8')
        else:
            return out