Package org.haystack.server

Source Code of org.haystack.server.HServer

//
// Copyright (c) 2011, Brian Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   03 Nov 2011  Brian Frank  Creation
//
package org.haystack.server;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.haystack.HCol;
import org.haystack.HDateTimeRange;
import org.haystack.HDict;
import org.haystack.HDictBuilder;
import org.haystack.HFilter;
import org.haystack.HGrid;
import org.haystack.HGridBuilder;
import org.haystack.HHisItem;
import org.haystack.HTimeZone;
import org.haystack.HWatch;
import org.haystack.ParseException;
import org.haystack.UnknownNameException;
import org.haystack.UnknownRecException;
import org.haystack.UnknownWatchException;
import org.haystack.io.HGridWriter;
import org.haystack.tagval.HDateTime;
import org.haystack.tagval.HNum;
import org.haystack.tagval.HRef;
import org.haystack.tagval.HUri;
import org.haystack.tagval.HVal;
import org.haystack.temp.HProj;

/**
* HServer is the interface between HServlet and a database of
* tag based entities. All methods on HServer must be thread safe.
*
* @see <a href='http://project-haystack.org/doc/Rest'>Project Haystack</a>
*/
public abstract class HServer extends HProj {

    //////////////////////////////////////////////////////////////////////////
    // Operations
    //////////////////////////////////////////////////////////////////////////

    /**
     * Return the operations supported by this database.
     */
    public abstract HOp[] ops();

    /**
     * Lookup an operation by name. If no operation is registered
     * for the given name, then return null or raise UnknownNameException
     * base on check flag.
     */
    public HOp op(String name, boolean checked) {
        // lazily build lookup map
        if (this.opsByName == null) {
            Map<String, HOp> map = new HashMap<String, HOp>();
            HOp[] ops = ops();
            for (int i = 0; i < ops.length; ++i) {
                HOp op = ops[i];
                if (map.get(op.name()) != null)
                    System.out.println("WARN: duplicate HOp name: " + op.name());
                map.put(op.name(), op);
            }
            this.opsByName = map;
        }

        // lookup
        HOp op = opsByName.get(name);
        if (op != null)
            return op;
        if (checked)
            throw new UnknownNameException(name);
        return null;
    }

    //////////////////////////////////////////////////////////////////////////
    // About
    //////////////////////////////////////////////////////////////////////////

    /**
     * Get the about metadata which should contain following tags:
     */
    @Override
    public final HDict about() {
        return new HDictBuilder().add(onAbout()).add("haystackVersion", "2.0").add("serverTime", HDateTime.now())
                .add("serverBootTime", this.bootTime).add("tz", HTimeZone.getDefault().name).toDict();
    }

    /**
     * Implementation hook for "about" method.
     * Should return these tags:
     * - serverName: Str
     * - productName: Str
     * - productVersion: Str
     * - productUri: Uri
     * - moduleName: Str
     * - moduleVersion: Str
     * - moduleUri: Uri
     */
    protected abstract HDict onAbout();

    //////////////////////////////////////////////////////////////////////////
    // Reads
    //////////////////////////////////////////////////////////////////////////

    /**
     * Default implementation routes to onReadById
     */
    @Override
    protected HGrid onReadByIds(HRef[] ids) {
        HDict[] recs = new HDict[ids.length];
        for (int i = 0; i < ids.length; ++i)
            recs[i] = onReadById(ids[i]);
        return HGridBuilder.dictsToGrid(recs);
    }

    /**
     * Default implementation scans all records using "iterator"
     */
    @Override
    protected HGrid onReadAll(String filter, int limit) {
        HFilter f = HFilter.make(filter);
        List<HDict> acc = new ArrayList<HDict>();
        for (Iterator<HDict> it = iterator(); it.hasNext();) {
            HDict rec = it.next();
            if (f.include(rec, filterPather)) {
                acc.add(rec);
                if (acc.size() >= limit)
                    break;
            }
        }
        return HGridBuilder.dictsToGrid(acc.toArray(new HDict[acc.size()]));
    }

    private final HFilter.Pather filterPather = new HFilter.Pather() {
        @Override
        public HDict find(String id) {
            return readById(HRef.make(id));
        }
    };

    /**
     * Implementation hook to iterate every entity record in
     * the database as a HDict.
     */
    protected abstract Iterator<HDict> iterator();

    //////////////////////////////////////////////////////////////////////////
    // Navigation
    //////////////////////////////////////////////////////////////////////////

    /**
     * Return navigation children for given navId.
     */
    public HGrid nav(String navId) {
        return onNav(navId);
    }

    /**
     * Return navigation tree children for given navId.
     * The grid must define the "navId" column.
     */
    protected abstract HGrid onNav(String navId);

    /**
     * Read a record from the database using a navigation path.
     * If not found then return null or raise UnknownRecException
     * base on checked flag.
     */
    public HDict navReadByUri(HUri uri, boolean checked) {
        HDict rec = onNavReadByUri(uri);
        if (rec != null)
            return rec;
        if (checked)
            throw new UnknownRecException(uri.toString());
        return null;
    }

    /**
     * Implementation hook for navReadByUri. Return null if not
     * found. Do NOT raise any exceptions.
     */
    protected abstract HDict onNavReadByUri(HUri uri);

    //////////////////////////////////////////////////////////////////////////
    // Watches
    //////////////////////////////////////////////////////////////////////////

    /**
     * Create a new watch with an empty subscriber list. The dis
     * string is a debug string to keep track of who created the watch.
     */
    @Override
    public final HWatch watchOpen(String dis) {
        dis = dis.trim();
        if (dis.length() == 0)
            throw new IllegalArgumentException("dis is empty");
        return onWatchOpen(dis);
    }

    /**
     * List the open watches.
     */
    @Override
    public final HWatch[] watches() {
        return onWatches();
    }

    /**
     * Lookup a watch by its unique identifier. If not found then
     * raise UnknownWatchErr or return null based on checked flag.
     */
    @Override
    public HWatch watch(String id, boolean checked) {
        HWatch w = onWatch(id);
        if (w != null)
            return w;
        if (checked)
            throw new UnknownWatchException(id);
        return null;
    }

    /**
     * Implementation hook for watchOpen.
     */
    protected abstract HWatch onWatchOpen(String dis);

    /**
     * Implementation hook for watches.
     */
    protected abstract HWatch[] onWatches();

    /**
     * Implementation hook for watch lookup, return null if not found.
     */
    protected abstract HWatch onWatch(String id);

    //////////////////////////////////////////////////////////////////////////
    // Point Writes
    //////////////////////////////////////////////////////////////////////////

    /**
     * Return priority array for writable point identified by id.
     * The grid contains 17 rows with following columns:
     * - level: number from 1 - 17 (17 is default)
     * - levelDis: human description of level
     * - val: current value at level or null
     * - who: who last controlled the value at this level
     */
    public final HGrid pointWriteArray(HRef id) {
        // lookup entity
        HDict rec = readById(id);

        // check that entity has "writable" tag
        if (rec.missing("writable"))
            throw new UnknownNameException("Rec missing 'writable' tag: " + rec.dis());

        return onPointWriteArray(rec);
    }

    /**
     * Write to the given priority array level.
     */
    public final void pointWrite(HRef id, int level, HVal val, String who, HNum dur) {
        // argument checks
        if (level < 1 || level > 17)
            throw new IllegalArgumentException("Invalid level 1-17: " + level);
        if (who == null)
            throw new IllegalArgumentException("who is null");

        // lookup entity
        HDict rec = readById(id);

        // check that entity has "writable" tag
        if (rec.missing("writable"))
            throw new UnknownNameException("Rec missing 'writable' tag: " + rec.dis());

        onPointWrite(rec, level, val, who, dur);
    }

    /**
     * Implementation hook for pointWriteArray
     */
    protected abstract HGrid onPointWriteArray(HDict rec);

    /**
     * Implementation hook for pointWrite
     */
    protected abstract void onPointWrite(HDict rec, int level, HVal val, String who, HNum dur);

    //////////////////////////////////////////////////////////////////////////
    // History
    //////////////////////////////////////////////////////////////////////////

    /**
     * Read history time-series data for given record and time range. The
     * items returned are exclusive of start time and inclusive of end time.
     * Raise exception if id does not map to a record with the required tags
     * "his" or "tz". The range may be either a String or a HDateTimeRange.
     * If HTimeDateRange is passed then must match the timezone configured on
     * the history record. Otherwise if a String is passed, it is resolved
     * relative to the history record's timezone.
     */
    public final void hisRead(HRef id, Object range, HGridWriter writer) {
        // lookup entity
        HDict rec = readById(id);

        // check that entity has "his" tag
        if (rec.missing("his"))
            throw new UnknownNameException("Rec missing 'his' tag: " + rec.dis());

        // lookup "tz" on entity
        HTimeZone tz = null;
        if (rec.has("tz"))
            tz = HTimeZone.make(rec.getStr("tz"), false);
        if (tz == null)
            throw new UnknownNameException("Rec missing or invalid 'tz' tag: " + rec.dis());

        // check or parse date range
        HDateTimeRange r = null;
        if (range instanceof HDateTimeRange) {
            r = (HDateTimeRange) range;
        }
        else {
            try {
                r = HDateTimeRange.make(range.toString(), tz);
            }
            catch (ParseException e) {
                throw new ParseException("Invalid date time range: " + range);
            }
        }

        // checking
        if (!r.start.tz.equals(tz))
            throw new RuntimeException("range.tz != rec: " + r.start.tz + " != " + tz);

        // build and return result grid
        HDict meta = new HDictBuilder().add("id", id).add("hisStart", r.start).add("hisEnd", r.end).toDict();
        writer.writeMeta(meta);

        HDictBuilder dictBuilder = new HDictBuilder();
        HCol[] cols = { new HCol(0, "ts", dictBuilder.toDict()), new HCol(1, "val", dictBuilder.toDict()), };
        writer.writeCols(cols);

        // route to subclass
        onHisRead(rec, r, writer, cols);

        // TODO why is this here?
        //        // check items
        //        if (items.length > 0) {
        //            if (r.start.millis() >= items[0].ts.millis())
        //                throw new IllegalStateException("start range not met");
        //            if (r.end.millis() < items[items.length - 1].ts.millis())
        //                throw new IllegalStateException("end range not met");
        //        }
    }

    /**
     * Implementation hook for hisRead. The items must be exclusive
     * of start and inclusive of end time.
     */
    protected abstract void onHisRead(HDict rec, HDateTimeRange range, HGridWriter writer, HCol[] cols);

    /**
     * Write a set of history time-series data to the given point record.
     * The record must already be defined and must be properly tagged as
     * a historized point. The timestamp timezone must exactly match the
     * point's configured "tz" tag. If duplicate or out-of-order items are
     * inserted then they must be gracefully merged.
     */
    @Override
    public final void hisWrite(HRef id, HHisItem[] items) {
        // lookup entity
        HDict rec = readById(id);

        // check that entity has "his" tag
        if (rec.missing("his"))
            throw new UnknownNameException("Entity missing 'his' tag: " + rec.dis());

        // lookup "tz" on entity
        HTimeZone tz = null;
        if (rec.has("tz"))
            tz = HTimeZone.make(rec.getStr("tz"), false);
        if (tz == null)
            throw new UnknownNameException("Rec missing or invalid 'tz' tag: " + rec.dis());

        // check tz of items
        if (items.length == 0)
            return;
        for (int i = 0; i < items.length; ++i)
            if (!items[i].ts.tz.equals(tz))
                throw new RuntimeException("item.tz != rec.tz: " + items[i].ts.tz + " != " + tz);

        // route to subclass
        onHisWrite(rec, items);
    }

    /**
     * Implementation hook for onHisWrite.
     */
    protected abstract void onHisWrite(HDict rec, HHisItem[] items);

    //////////////////////////////////////////////////////////////////////////
    // Actions
    //////////////////////////////////////////////////////////////////////////

    /**
     * Invoke an action identified by id and action.
     */
    public final HGrid invokeAction(HRef id, String action, HDict args) {
        // lookup entity
        HDict rec = readById(id);

        // route to subclass
        return onInvokeAction(rec, action, args);
    }

    /**
     * Implementation hook for invokeAction
     */
    protected abstract HGrid onInvokeAction(HDict rec, String action, HDict args);

    //////////////////////////////////////////////////////////////////////////
    // Fields
    //////////////////////////////////////////////////////////////////////////

    final HDateTime bootTime = HDateTime.now();
    private Map<String, HOp> opsByName;

}
TOP

Related Classes of org.haystack.server.HServer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.