com.diversityarrays.dal.server.DalServer.java Source code

Java tutorial

Introduction

Here is the source code for com.diversityarrays.dal.server.DalServer.java

Source

/*
 * dalserver-interop library - implementation of DAL server for interoperability
 * Copyright (C) 2015  Diversity Arrays Technology
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.diversityarrays.dal.server;

import java.awt.Image;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.prefs.Preferences;

import javax.imageio.ImageIO;
import javax.imageio.spi.ServiceRegistry;
import javax.swing.SwingUtilities;

import net.pearcan.ui.GuiUtil;
import net.pearcan.ui.desktop.MacApplication;
import net.pearcan.ui.desktop.MacApplicationException;
import net.pearcan.util.Util;

import org.apache.commons.collections15.Closure;

import com.diversityarrays.dal.db.AuthenticationException;
import com.diversityarrays.dal.db.DalDatabase;
import com.diversityarrays.dal.db.DalDatabaseUtil;
import com.diversityarrays.dal.db.DalDbException;
import com.diversityarrays.dal.db.DalResponseBuilder;
import com.diversityarrays.dal.db.DbUtil;
import com.diversityarrays.dal.db.SqlDalDatabase;
import com.diversityarrays.dal.db.SystemGroupInfo;
import com.diversityarrays.dal.db.UserInfo;
import com.diversityarrays.dal.ops.DalOperation;
import com.diversityarrays.dal.ops.OperationMatch;
import com.diversityarrays.dal.ops.WordNode;
import com.diversityarrays.dal.service.DalDbProviderService;
import com.diversityarrays.dal.service.Parameter;
import com.diversityarrays.dal.service.ParameterValue;
import com.diversityarrays.dal.sqldb.SqlUtil;
import com.diversityarrays.dalclient.DALClient;
import com.diversityarrays.dalclient.SessionExpiryOption;

import fi.iki.elonen.NanoHTTPD;
import fi.iki.elonen.NanoHTTPD.Response.IStatus;
import fi.iki.elonen.SimpleWebServer;

public class DalServer extends SimpleWebServer implements IDalServer {

    private static final String YOU_NEED_TO_LOGIN_FIRST = "You need to login first";

    static final private String DAL_SERVER_VERSION = "1.0.1";

    static private void fatal(String msg) {
        System.err.println("?" + msg);
        giveHelpThenExit(1);
    }

    static private final String[] CMD_HELP_LINES = {
            "Usage: java -jar dalserver.jar [OPTIONS] [databaseServiceName]", "Options are:",
            "--            ignore everything after this 'option'", "-version      print version number and exit",
            "-help         print this help and exit", "-expire NN    auto-expiry minutes for sessions",
            "-localhost    listen on localhost:PORT instead of <ip-address>:PORT",
            "-port PORT    specifies the port number to listen on (default is "
                    + DalServerUtil.DEFAULT_DAL_SERVER_PORT + ")",
            "", "-docroot DIR  specifies a folder from which to service files", };

    public static void giveHelpThenExit(int code) {
        for (String line : CMD_HELP_LINES) {
            System.out.println(line);
        }
        System.out.println("Available databaseServiceNames are:");
        for (Iterator<DalDbProviderService> iter = ServiceRegistry.lookupProviders(DalDbProviderService.class); iter
                .hasNext();) {
            DalDbProviderService provider = iter.next();
            System.out.println("\t" + provider.getProviderName());
        }
        System.exit(code);
    }

    public static void main(String[] args) {

        String host = null;
        int port = DalServerUtil.DEFAULT_DAL_SERVER_PORT;

        int inactiveMins = DalServerUtil.DEFAULT_MAX_INACTIVE_MINUTES;

        File docRoot = null;

        String serviceName = null;

        for (int i = 0; i < args.length; ++i) {
            String argi = args[i];
            if (argi.startsWith("-")) {
                if ("--".equals(argi)) {
                    break;
                }

                if ("-version".equals(argi)) {
                    System.out.println(DAL_SERVER_VERSION);
                    System.exit(0);
                }

                if ("-help".equals(argi)) {
                    giveHelpThenExit(0);
                }

                if ("-docroot".equals(argi)) {
                    if (++i >= args.length || args[i].startsWith("-")) {
                        fatal("missing value for " + argi);
                    }
                    docRoot = new File(args[i]);
                } else if ("-sqllog".equals(argi)) {
                    SqlUtil.logger = Logger.getLogger(SqlUtil.class.getName());
                } else if ("-expire".equals(argi)) {
                    if (++i >= args.length || args[i].startsWith("-")) {
                        fatal("missing value for " + argi);
                    }

                    try {
                        inactiveMins = Integer.parseInt(args[i], 10);
                        if (inactiveMins <= 0) {
                            fatal("invalid minutes: " + args[i]);
                        }
                    } catch (NumberFormatException e) {
                        fatal("invalid minutes: " + args[i]);
                    }
                } else if ("-localhost".equals(argi)) {
                    host = "localhost";
                } else if ("-port".equals(argi)) {
                    if (++i >= args.length || args[i].startsWith("-")) {
                        fatal("missing value for " + argi);
                    }
                    try {
                        port = Integer.parseInt(args[i], 10);
                        if (port < 0 || port > 65535) {
                            fatal("invalid port number: " + args[i]);
                        }
                    } catch (NumberFormatException e) {
                        fatal("invalid port number: " + args[i]);
                    }
                } else {
                    fatal("invalid option: " + argi);
                }
            } else {
                if (serviceName != null) {
                    fatal("multiple serviceNames not supported: " + argi);
                }
                serviceName = argi;
            }
        }

        final DalServerPreferences preferences = new DalServerPreferences(
                Preferences.userNodeForPackage(DalServer.class));

        if (docRoot == null) {
            docRoot = preferences.getWebRoot(new File(System.getProperty("user.dir"), "www"));
        }

        DalServer server = null;

        if (serviceName != null && docRoot.isDirectory()) {
            try {
                DalDatabase db = createDalDatabase(serviceName, preferences);
                if (db.isInitialiseRequired()) {
                    Closure<String> progress = new Closure<String>() {
                        @Override
                        public void execute(String msg) {
                            System.out.println("Database Initialisation: " + msg);
                        }
                    };
                    db.initialise(progress);
                }
                server = create(preferences, host, port, docRoot, db);
            } catch (NoServiceException e) {
                throw new RuntimeException(e);
            } catch (DalDbException e) {
                throw new RuntimeException(e);
            }
        }

        Image serverIconImage = null;
        InputStream imageIs = DalServer.class.getResourceAsStream("dalserver-24.png");
        if (imageIs != null) {
            try {
                serverIconImage = ImageIO.read(imageIs);

                if (Util.isMacOS()) {
                    try {
                        MacApplication macapp = new MacApplication(null);
                        macapp.setDockIconImage(serverIconImage);
                    } catch (MacApplicationException e) {
                        System.err.println(e.getMessage());
                    }
                }

            } catch (IOException ignore) {
            }
        }

        if (server != null) {
            server.setMaxInactiveMinutes(inactiveMins);
        } else {
            AskServerParams asker = new AskServerParams(serverIconImage, null, "DAL Server Start", docRoot,
                    preferences);
            GuiUtil.centreOnScreen(asker);
            asker.setVisible(true);

            if (asker.cancelled) {
                System.exit(0);
            }

            host = asker.dalServerHostName;
            port = asker.dalServerPort;
            inactiveMins = asker.maxInactiveMinutes;

            server = create(preferences, host, port, asker.wwwRoot, asker.dalDatabase);
            // server.setUseSimpleDatabase(asker.useSimpleDatabase);
        }

        final DalServer f_server = server;
        final File f_wwwRoot = docRoot;
        final Image f_serverIconImage = serverIconImage;

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                DalServerFactory factory = new DalServerFactory() {
                    @Override
                    public DalServer create(String hostName, int port, File wwwRoot, DalDatabase dalDatabase) {
                        return DalServer.create(preferences, hostName, port, wwwRoot, dalDatabase);
                    }
                };
                ServerGui gui = new ServerGui(f_serverIconImage, f_server, factory, f_wwwRoot, preferences);
                gui.setVisible(true);
            }
        });

    }

    /**
     * 
     * @param databaseFolder
     * @param databaseServer
     * @param databasePort
     * @param wwwRoot
     * @param host
     * @param serviceName
     * @param prefs
     * @param errors
     * @throws IllegalArgumentException if no provider or unable to create
     * @return
     * @throws NoServiceException 
     */
    private static DalDatabase createDalDatabase(String dalDbServiceName, DalServerPreferences prefs)
            throws NoServiceException {
        DalDbProviderService provider = null;
        for (Iterator<DalDbProviderService> iter = ServiceRegistry.lookupProviders(DalDbProviderService.class); iter
                .hasNext();) {
            provider = iter.next();
            if (dalDbServiceName.equals(provider.getProviderName())) {
                break;
            }
        }

        if (provider == null) {
            throw new NoServiceException("No provider for " + dalDbServiceName);
        }

        Set<Parameter<?>> required = provider.getParametersRequired();
        Map<Parameter<?>, Throwable> errors = new LinkedHashMap<Parameter<?>, Throwable>();
        Map<Parameter<?>, ParameterValue<?>> savedSettings = prefs.loadSavedSettings(provider, required, errors);

        if (!errors.isEmpty()) {
            StringBuilder sb = new StringBuilder("Parameter errors:");
            for (Parameter<?> p : errors.keySet()) {
                sb.append("\n  ").append(p.name).append(errors.get(p).getMessage());
            }
            throw new NoServiceException(sb.toString());
        }

        Set<ParameterValue<?>> parameterValues = new LinkedHashSet<ParameterValue<?>>();
        for (Parameter<?> p : required) {
            ParameterValue<?> v = savedSettings.get(p);
            if (v != null) {
                parameterValues.add(v);
            }
        }

        try {
            Closure<String> progress = new Closure<String>() {
                @Override
                public void execute(String msg) {
                    System.out.println(msg);
                }
            };
            return provider.createDatabase(parameterValues, progress, false);

        } catch (DalDbException e) {
            throw new NoServiceException("provider " + provider.getProviderName() + " couldn't create DalDatabase",
                    e);
        }
    }

    private boolean useSimpleDatabase;

    public void setUseSimpleDatabase(boolean b) {
        useSimpleDatabase = b;
    }

    @Override
    public boolean getUseSimpleDatabase() {
        return useSimpleDatabase;
    }

    static public DalServer create(DalServerPreferences prefs, String host, int port, File wwwroot,
            DalDatabase dd) {

        if (host == null) {
            try {
                host = InetAddress.getLocalHost().getHostAddress();
            } catch (UnknownHostException e) {
                host = "localhost";
            }
        }

        return new DalServer(prefs, host, port, wwwroot, dd);
    }

    private boolean verbose;
    private final DalDatabase dalDatabase;
    private WordNode wordNodeRoot = new WordNode();

    private DalSessionStore dalSessionStore = new DalSessionStore();

    private int maximumInactivityMinutes = DalServerUtil.DEFAULT_MAX_INACTIVE_MINUTES;
    private long maximumInactivityMillis = maximumInactivityMinutes * 60 * 1000L;

    @SuppressWarnings("unused")
    private final DalServerPreferences preferences;;

    public DalServer(DalServerPreferences prefs, String host, int port, File wwwroot, DalDatabase dd) {
        super(host, port, wwwroot, true);
        this.preferences = prefs;
        this.dalDatabase = dd;

        DalServerUtil.buildWordTree(dalDatabase.getOperations(), wordNodeRoot);
    }

    public NanoHTTPD getHttpServer() {
        return this;
    }

    @Override
    public void setMaxInactiveMinutes(int mins) {
        this.maximumInactivityMinutes = mins;
        this.maximumInactivityMillis = mins * 60 * 1000L;
    }

    public int getMaxInactiveMinutes() {
        return maximumInactivityMinutes;
    }

    public String getDalServerVersion() {
        return DAL_SERVER_VERSION;
    }

    public int getDalOperationCount() {
        return dalDatabase.getOperations().size();
    }

    public DalDatabase getDalDatabase() {
        return dalDatabase;
    }

    public boolean isQuiet() {
        return !isVerbose();
    }

    public void setQuiet(boolean b) {
        setVerbose(!b);
    }

    public boolean isVerbose() {
        return verbose;
    }

    public void setVerbose(boolean b) {
        verbose = b;
    }

    public Response serve(IHTTPSession session) {

        Map<String, String> filePathByName = new HashMap<String, String>();
        Method method = session.getMethod();
        if (Method.PUT.equals(method) || Method.POST.equals(method)) {
            try {
                session.parseBody(filePathByName);
            } catch (IOException ioe) {
                ioe.printStackTrace();
                return new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT,
                        "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
            } catch (ResponseException re) {
                re.printStackTrace();
                return new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
            }
        }

        Response r = serveImpl(session.getUri(), method, session, filePathByName);

        r.addHeader("Access-Control-Allow-Methods", "GET, POST");
        // r.addHeader("Access-Control-Allow-Credentials", "true");
        r.addHeader("Access-Control-Allow-Origin", "*");
        r.addHeader("X-XSS-Protection", "0"); // supposedly to allow Safari debugging
        // r.addHeader("Access-Control-Allow-Headers", "Content-Type, *");

        return r;
    }

    private Response serveImpl(String uri, Method method, IHTTPSession session,
            Map<String, String> filePathByName) {
        Response result;

        boolean wantJson = "json".equals(session.getParms().get("ctype"));

        if (verbose) {
            System.out.println(method + " '" + uri + "' ");

            Map<String, String> headers = session.getHeaders();
            Iterator<String> e = headers.keySet().iterator();
            while (e.hasNext()) {
                String value = e.next();
                System.out.println("  HDR: '" + value + "' = '" + headers.get(value) + "'");
            }

            Map<String, String> parms = session.getParms();
            e = parms.keySet().iterator();
            while (e.hasNext()) {
                String value = e.next();
                System.out.println("  PRM: '" + value + "' = '" + parms.get(value) + "'");
            }
        }

        if ("/help".equals(uri)) {
            result = giveHelp();
        } else if (uri.equals("/sessions")) {
            result = doListSessions();
        } else if (Method.GET.equals(method) && uri.startsWith("/entity:")) {
            result = doEntityInfo(uri.substring(8));
        } else if (Method.POST.equals(method) && uri.endsWith("/entity")) {
            result = doEntityInfo(session.getParms().get("entity"));
        } else if (Method.GET.equals(method) && uri.startsWith("/sql:")) {
            if (dalDatabase instanceof SqlDalDatabase) {
                result = createSqlQueryResponse((SqlDalDatabase) dalDatabase, uri.substring(5), null);
            } else {
                result = DalServerUtil.buildNotSqlDalDatabaseTextResponse(dalDatabase);
            }
        } else if (Method.POST.equals(method) && uri.endsWith("/sql")) {
            if (dalDatabase instanceof SqlDalDatabase) {
                result = createSqlQueryResponse((SqlDalDatabase) dalDatabase, session.getParms().get("sql"), null);
            } else {
                result = DalServerUtil.buildNotSqlDalDatabaseTextResponse(dalDatabase);
            }
        } else if (Method.GET.equals(method) && uri.startsWith("/table:")) {
            result = doTable(uri.substring(7));
        } else if (Method.POST.equals(method) && uri.endsWith("/table")) {
            result = doTable(session.getParms().get("table"));
        } else if (uri.startsWith("/dal/")) {
            String[] returnSql = new String[1];
            result = doDal(wantJson, method, uri.substring(5), session, filePathByName, returnSql);
            if (result != null) {
                IStatus status = result.getStatus();
                String desc = status.getDescription();
                System.out.println("\tresult.status=" + desc);
                if (returnSql[0] != null) {
                    System.out.println("\tSQL: " + returnSql[0]);
                }
            }
        } else {
            result = super.serve(session);
        }

        return result;
    }

    private Response doListSessions() {

        DalSession[] sessions = dalSessionStore.getSessions();

        StringBuilder sb = new StringBuilder("<html><body>");
        sb.append("<h2>Active Sessions:").append(sessions.length).append("</h2>");
        if (sessions.length > 0) {
            emitSessions(sessions, sb);
        }
        sb.append("</body></html>");

        return new Response(Response.Status.OK, MIME_HTML, sb.toString());
    }

    private void emitSessions(DalSession[] sessions, StringBuilder sb) {
        DateFormat df = new SimpleDateFormat("yyyy-MM-DD HH:mm:ss");

        sb.append("<table border='1'>");
        sb.append("<thead><tr>");
        sb.append("<th>Username</th>");
        sb.append("<th>User Id</th>");
        sb.append("<th>Last Active</th>");
        sb.append("<th>Expiry Option</th>");
        sb.append("<th>WriteToken</th>");
        sb.append("<th>Session Id</th>");
        sb.append("</tr></thead><tbody>");

        for (DalSession sess : sessions) {
            sb.append("<tr>").append("<td>").append(DbUtil.htmlEscape(sess.getUserName())).append("</td>")
                    .append("<td>").append(sess.getUserId()).append("</td>").append("<td>")
                    .append(df.format(sess.getLastActive())).append("</td>").append("<td>")
                    .append(sess.sessionExpiryOption).append("</td>").append("<td>").append(sess.writeToken)
                    .append("</td>").append("<td>").append(sess.sessionId).append("</td>").append("</tr>");
        }
        sb.append("</tbody></table>");
    }

    private Response doDal(boolean wantJson, Method method, String dalcmd, IHTTPSession session,
            Map<String, String> filePathByName, String[] returnSql) {

        System.out.println("DAL: " + dalcmd);

        Response result;

        CookieHandler cookies = session.getCookies();
        String sessionId = cookies.read(DalSession.COOKIE_NAME_DAL_SESSION_ID);

        DalSession dalSession = null;
        if (sessionId != null) {
            dalSession = dalSessionStore.getSession(sessionId);

            if (dalSession == null) {
                System.out.println("\tNO session for sessionId=" + sessionId);
            } else {
                System.out.println("\tsession: " + dalSession);
                if (dalSession.hasExpired(maximumInactivityMillis)) {
                    System.out.println("\t **expired**");
                    dalSessionStore.removeSession(dalSession);
                    // But if they are trying to login again, this isn't an error !
                    if (!dalcmd.startsWith(DalOperation.LOGIN_STEM)) {
                        return DalServerUtil.buildAuthErrorResponse(wantJson,
                                "Session expired. Please login again.");
                    }
                    dalSession = null;
                }
            }
        }

        if (dalcmd.startsWith(DalOperation.LOGIN_STEM)) {
            if (dalSession == null) {
                result = handleLogin(wantJson, dalcmd, session);
            } else {
                removeCookies(session);
                dalSessionStore.removeSession(dalSession);
                System.out.println("!!! Removed session: " + dalSession);
                result = DalServerUtil.buildAuthErrorResponse(wantJson,
                        "You were already logged-in as " + dalSession.getUserName() + ". You are now logged-out");
                // result = DalServerUtil.buildAuthErrorResponse(wantJson, "Already login.");
            }
        } else if (dalcmd.equals(DalOperation.LOGOUT)) {
            if (dalSession != null) {
                result = handleLogout(dalSession, wantJson, dalcmd, session);
                dalSessionStore.removeSession(dalSession);
                System.out.println("!!! Removed session: " + dalSession);
            } else {
                // TODO check if need to do: removeCookies(session);
                result = DalServerUtil.buildAuthErrorResponse(wantJson, "You are not logged-in");
            }
        } else if (dalcmd.equals(DalOperation.GET_LOGIN_STATUS)) {
            result = handleGetLoginStatus(wantJson, dalSession);
        } else if (dalcmd.equals(DalOperation.GET_VERSION)) {
            // We can do this regardless of login status
            result = createGetVersionResult(wantJson, dalSession);
        } else if (dalSession == null) {
            // If there isn't a session you can get past here!
            result = DalServerUtil.buildAuthErrorResponse(wantJson, "(0) " + YOU_NEED_TO_LOGIN_FIRST);
        } else {
            if (dalSession != null) {
                dalSession.delayExpiry();
            }

            if (DalOperation.LIST_OPERATION.equals(dalcmd)) {
                result = doListOperation(wantJson);
            } else if (DalOperation.LIST_GROUP.equals(dalcmd) || DalOperation.LIST_ALL_GROUP.equals(dalcmd)) {
                result = handleListOrListAllGroup(wantJson, dalcmd, dalSession, returnSql);
            } else if (dalcmd.endsWith(DalOperation.LIST_FIELD_TAIL)) {
                result = handleListField(wantJson, dalcmd, dalSession);
            } else if (dalcmd.startsWith(DalOperation.SWITCH_GROUP_STEM)) {
                result = handleSwitchGroup(wantJson, dalcmd, dalSession);
            } else if (dalcmd.equals(DalOperation.GET_VERSION)) {
                // Should have already been done but here for completeness
                result = createGetVersionResult(wantJson, dalSession);
            } else {
                StringBuilder errmsg = new StringBuilder();
                OperationMatch match = getOperationMatch(dalcmd, errmsg);
                if (match == null || match.node.getOperation() == null) {
                    result = DalServerUtil.buildNotFoundResponse(wantJson,
                            "No matching operation for '" + dalcmd + "' (" + errmsg + ")");
                } else {
                    result = doOperation(dalSession, wantJson, match, method, dalcmd, session, filePathByName);
                }
            }
        }
        return result;
    }

    public Response createGetVersionResult(boolean wantJson, DalSession session) {
        try {
            return DalServerUtil.createBuilder(wantJson).startTag(DALClient.TAG_INFO)
                    .attribute("Copyright",
                            "Copyright (c) 2011-2014, Diversity Arrays Technology, All rights reserved.")
                    .attribute(DALClient.ATTR_VERSION, dalDatabase.getDatabaseVersion(session))
                    .attribute("ServerVersion", DAL_SERVER_VERSION).attribute("About", "Data Access Layer").endTag()
                    .build(Response.Status.OK);
        } catch (DalDbException e) {
            return DalServerUtil.buildInternalErrorResponse(wantJson, e);
        }
    }

    public Response handleGetLoginStatus(boolean wantJson, DalSession dalSession) {

        Response result;

        DalResponseBuilder builder = DalServerUtil.createBuilder(wantJson);

        builder.startTag(DALClient.TAG_INFO);

        if (dalSession == null) {
            builder.attribute(DALClient.ATTR_GROUP_SELECTION_STATUS, "0").attribute(DALClient.ATTR_LOGIN_STATUS,
                    "0");
        } else {
            builder.attribute(DALClient.ATTR_GROUP_SELECTION_STATUS, dalSession.getGroupId() == null ? "0" : "1")
                    .attribute(DALClient.ATTR_LOGIN_STATUS, "1"); // dalSession implies logged-in
        }

        builder.endTag();

        result = builder.build(Response.Status.OK);
        return result;
    }

    private Response handleSwitchGroup(boolean wantJson, String dalcmd, DalSession dalSession) {
        Response result;

        // TODO pass this on to dalDatabase.switchGroup() in case it is a forwarder
        //      and react appropriately

        String[] parts = dalcmd.split("/");
        if (parts.length == 3 && parts[2].matches("\\d+")) {
            String groupId = parts[2];

            try {
                // Does the database have this group?
                SystemGroupInfo groupInfo = dalDatabase.getSystemGroupInfo(dalSession);
                if (groupInfo == null) {
                    result = DalServerUtil.buildAuthErrorResponse(wantJson,
                            "User " + dalSession.getUserId() + " is not a member of group " + groupId);
                } else {
                    // Yup - and this user is a member...
                    dalSession.setGroupId(groupInfo.getGroupId());

                    result = DalServerUtil.createBuilder(wantJson).startTag(DALClient.TAG_INFO)
                            .attribute(DALClient.ATTR_MESSAGE,
                                    "You have been switched to " + groupId + " successfully.")
                            .attribute(DALClient.ATTR_GROUP_NAME, groupInfo.getGroupName())
                            .attribute(DALClient.ATTR_GADMIN, groupInfo.isGroupOwner() ? "TRUE" : "FALSE").endTag()
                            .build(Response.Status.OK);
                }
            } catch (DalDbException e) {
                result = DalServerUtil.buildInternalErrorResponse(wantJson, e);
            }
        } else {
            result = DalServerUtil.buildNotFoundResponse(wantJson, "Invalid command: " + dalcmd);
        }

        return result;
    }

    private Response handleListOrListAllGroup(boolean wantJson, String dalcmd, DalSession session,
            String[] returnSql) {
        Response result = null;

        DalResponseBuilder builder = DalServerUtil.createBuilder(wantJson);

        try {
            if (DalOperation.LIST_ALL_GROUP.equals(dalcmd)) {
                dalDatabase.performListAllGroup(session, builder, returnSql);
                result = builder.build(Response.Status.OK);
            } else if (DalOperation.LIST_GROUP.equals(dalcmd)) {

                dalDatabase.performListGroup(session, builder, returnSql);

                result = builder.build(Response.Status.OK);
            } else {
                // handled by if (result==null) below...
            }
        } catch (DalDbException e) {
            Throwable t = e.getCause();
            if (t == null) {
                t = e;
            }
            result = DalServerUtil.buildInternalErrorResponse(wantJson, t.getMessage() + ":" + dalcmd);
        }

        if (result == null) {
            result = DalServerUtil.buildNotFoundResponse(wantJson, "Unrecognised LIST command: " + dalcmd);
        }

        return result;
    }

    private Response handleLogout(DalSession dalSession, boolean wantJson, String dalcmd, IHTTPSession session) {
        Response r = null;
        try {
            dalDatabase.doLogout(dalSession);
        } finally {
            r = DalServerUtil.createBuilder(wantJson).startTag(DALClient.TAG_INFO)
                    .attribute(DALClient.ATTR_MESSAGE, "You have logged out successfully.").endTag()
                    .build(Response.Status.OK);

            removeCookies(session);
        }

        return r;
    }

    public void removeCookies(IHTTPSession session) {
        CookieHandler cookies = session.getCookies();
        for (String cookieName : DalSession.COOKIE_NAMES) {
            cookies.delete(cookieName);
        }
    }

    private Response handleLogin(boolean wantJson, String dalcmd, IHTTPSession session) {
        Response result;
        String[] parts = dalcmd.split("/");
        if (parts.length != 3) {
            result = DalServerUtil.buildAuthErrorResponse(wantJson, "(1) missing parameters");
        } else {
            String userName = parts[1];
            String seoName = parts[2];

            SessionExpiryOption seo = SessionExpiryOption.lookup(seoName);
            if (seo == null) {
                return DalServerUtil.buildAuthErrorResponse(wantJson, "(2) Invalid SessionExpiryOption=" + seoName);
            }

            try {
                String newSessionId = DalSession.createSessionId();
                UserInfo userInfo = dalDatabase.doLogin(newSessionId, userName, seo, session.getParms());

                DalSession dalSession = new DalSession(newSessionId, userInfo, seo);
                dalSessionStore.addSession(dalSession);

                result = DalServerUtil.createBuilder(wantJson)

                        .startTag(DALClient.TAG_USER).attribute(DALClient.ATTR_USER_ID, userInfo.getUserId())
                        .endTag()

                        .startTag(DALClient.TAG_WRITE_TOKEN).attribute(DALClient.ATTR_VALUE, dalSession.writeToken)
                        .endTag()

                        .build(Response.Status.OK);

                List<Cookie> cookies = dalSession.getCookies();
                if (cookies != null) {
                    System.out.println("Cookies for dalSession:" + dalSession);
                    for (Cookie cookie : cookies) {
                        System.out.println("\t" + cookie);
                        session.getCookies().set(cookie);
                    }
                }
            } catch (AuthenticationException ae) {
                Throwable e = ae.getCause();
                if (e == null) {
                    e = ae;
                }

                System.err.println(e.getMessage());
                result = DalServerUtil.buildAuthErrorResponse(wantJson, "(3) " + e.getMessage());
            }
        }
        return result;
    }

    private OperationMatch getOperationMatch(String dalcmd, StringBuilder errmsg) {

        OperationMatch result = null;

        StringBuilder entityErrmsg = new StringBuilder();
        StringBuilder nonEntityErrmsg = new StringBuilder();

        OperationMatch match = DalDatabaseUtil.findOperationMatch(dalcmd, wordNodeRoot, entityErrmsg);

        if (match.node != null) {
            result = match;
        } else {
            String e = entityErrmsg.length() > 0 ? entityErrmsg.toString() : nonEntityErrmsg.toString();
            errmsg.append(e);
        }

        return result;
    }

    private Response doEntityInfo(String entityName) {
        Response result;
        if (entityName == null || entityName.isEmpty()) {
            result = new Response(Response.Status.OK, MIME_PLAINTEXT,
                    "Entity Names:\n" + DbUtil.join("\n", dalDatabase.getEntityNames()));
        } else {
            String usedName = entityName.toLowerCase();
            List<DalOperation> ops = new ArrayList<DalOperation>();

            for (DalOperation op : dalDatabase.getOperations()) {
                if (entityName.equals(op.getEntityName())) {
                    ops.add(op);
                } else if (usedName.equals(op.getEntityName())) {
                    ops.add(op);
                }
            }

            StringBuilder sb = new StringBuilder("<html><body>");

            if (ops.isEmpty()) {
                sb.append("<b>No operations found for '").append(entityName).append("'");
            } else {
                sb.append("<b>Operations for '");
                sb.append(entityName).append("'</b>");
                if (!usedName.equals(entityName)) {
                    sb.append(" (as ").append(usedName).append(")");
                }
                sb.append("<ul>");
                for (DalOperation op : ops) {
                    sb.append("<li>").append(DbUtil.htmlEscape(op.getCommandTemplate())).append("</li>");
                }
                sb.append("</ul><hr/>");

                try {
                    if (dalDatabase instanceof SqlDalDatabase) {
                        SqlDalDatabase sqldb = (SqlDalDatabase) dalDatabase;
                        appendPossibleEntityTableDetails(sqldb, sqldb.createShowTableColumnsSql(entityName), sb);
                    }
                } catch (DalDbException e) {
                    sb.append("<hr/><b>Exception</b><br/>").append(DbUtil.htmlEscape(e.getMessage()));
                }
            }
            sb.append("</body></html>");

            result = new Response(Response.Status.OK, MIME_HTML, sb.toString());
        }
        return result;
    }

    static private void appendPossibleEntityTableDetails(SqlDalDatabase sqldb, String sql, StringBuilder sb)
            throws DalDbException {

        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {

            conn = sqldb.getConnection(false);

            stmt = conn.createStatement();

            // sb.append("<code>").append(DbUtil.htmlEscape(sql)).append("</code><hr/>");

            boolean hasResultSet = stmt.execute(sql);

            if (hasResultSet) {
                rs = stmt.getResultSet();
                DalServerUtil.appendResultSetRowsAsTable("No entity table", rs, sb);
            } else {
                int n = stmt.getUpdateCount();
                sb.append("Update count=").append(n);
            }
        } catch (SQLException e) {
            throw new DalDbException(e);
        } finally {
            SqlUtil.closeSandRS(stmt, rs);
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException ignore) {
                }
            }
        }

    }

    private Response doOperation(DalSession dalSession, boolean wantJson, OperationMatch match, Method method,
            String uri, IHTTPSession session, Map<String, String> filePathByName) {
        Response result;

        if (DalOperation.LIST_OPERATION.equals(match.node.getOperation().getCommandTemplate())) {
            /*
             * Note that we really shouldn't ever get here as this command should
             * have been detected and dispatched by our caller. 
             */
            result = doListOperation(wantJson);
        } else {
            // NOT "list/operation"
            DalOperation dalop = match.node.getOperation();
            List<String> dalOpParameters = collectDalOperationParameters(match, dalop);

            DalResponseBuilder responseBuilder = DalServerUtil.createBuilder(wantJson);

            try {
                dalop.execute(dalSession, responseBuilder, method, uri, dalOpParameters, session.getParms(),
                        filePathByName);
                result = responseBuilder.build(Response.Status.OK);
            } catch (AuthenticationException e) {
                result = DalServerUtil.buildAuthErrorResponse(wantJson, e.getMessage());
            } catch (DalDbException e) {
                Throwable t = e.getCause();
                if (t == null) {
                    t = e;
                }
                result = DalServerUtil.buildInternalErrorResponse(wantJson, t);
            }
        }

        return result;
    }

    /**
     * List all of the operations available for the DalDatabase we are serving.
     * @param wantJson
     * @return the Response
     */
    private Response doListOperation(boolean wantJson) {
        DalResponseBuilder builder = DalServerUtil.createBuilder(wantJson);
        builder.addResponseMeta("Operation");

        Set<String> seen = new HashSet<String>();
        for (String op : DalOperation.MORE_OPS) {
            seen.add(op);
            builder.startTag("Operation").attribute("REST", op).endTag();
        }

        for (DalOperation dalop : dalDatabase.getOperations()) {
            String op = dalop.getCommandTemplate();
            if (!seen.contains(op)) {
                builder.startTag("Operation").attribute("REST", op).endTag();
            }
        }

        return builder.build(Response.Status.OK);
    }

    // dalcmd ends with LIST_FIELD_TAIL
    private Response handleListField(boolean wantJson, String dalcmd, DalSession session) {
        Response result;

        String[] parts = dalcmd.split("/");
        if (parts.length != 3) {
            result = DalServerUtil.buildNotFoundResponse(wantJson, "Unknown operation: " + dalcmd);
        } else {
            String _tname = parts[0];
            try {
                DalResponseBuilder responseBuilder = DalServerUtil.createBuilder(wantJson);
                dalDatabase.performListField(session, _tname, responseBuilder);

                result = responseBuilder.build(Response.Status.OK);
            } catch (DalDbException e) {
                result = DalServerUtil.buildInternalErrorResponse(wantJson, e.getMessage());
            }
        }

        return result;
    }

    private List<String> collectDalOperationParameters(OperationMatch match, DalOperation dalop) {
        List<String> parameters = new ArrayList<String>();
        int nParameters = dalop.getParameterCount();
        for (int i = 0; i < nParameters; ++i) {
            if (i >= match.getParameterValueCount()) {
                break;
            }
            parameters.add(match.getParameterValue(i));
        }
        return parameters;
    }

    static private final String[] HELP_LINES = { "help             gives this output",
            // "dbinit[:NAMEs]   initialises the database (?yes to actually do the init instead of just showing the commands)",
            // "                 if supplied then NAMEs will only action the names provided",
            "sql:SQL          runs the SQL statement (SELECT/INSERT/UPDATE/DELETE)",
            "table:NAME       describes the NAMEd table or lists all tables if NAME is not supplied",
            "entity:NAME      lists the operations for NAME or all entity names if NAME is not supplied",
            "sessions         lists details of all sessions", "", "dal/...          are treated as DAL commands" };

    private Response giveHelp() {
        return new Response(Response.Status.OK, MIME_PLAINTEXT, DbUtil.join("\n", (Object[]) HELP_LINES));
    }

    private Response doTable(String tableCommand) {

        if (!(dalDatabase instanceof SqlDalDatabase)) {
            return DalServerUtil.buildNotSqlDalDatabaseTextResponse(dalDatabase);
        }

        SqlDalDatabase sqldb = (SqlDalDatabase) dalDatabase;

        String tableName = tableCommand.trim();

        String sql;
        if (tableName.isEmpty()) {
            sql = sqldb.createShowTablesSql();
        } else {
            sql = sqldb.createShowTableColumnsSql(tableName);
        }
        return createSqlQueryResponse(sqldb, sql, null);
    }

    private SqlWorker createSqlWorker(SqlDalDatabase sqldb) {
        SqlWorker w = new SqlWorker(sqldb);
        w.setVerbose(isVerbose());
        return w;
    }

    private Response createSqlQueryResponse(SqlDalDatabase sqldb, String sql, String metaTagName) {
        SqlWorker sqlWorker = createSqlWorker(sqldb);
        try {
            return sqlWorker.createResponse(SqlResponseType.TEXT, sql, metaTagName, null);
        } finally {
            sqlWorker.close();
        }
    }

}