You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2015/03/30 14:56:40 UTC

[11/22] isis git commit: ISIS-720: mothballing scimpi

http://git-wip-us.apache.org/repos/asf/isis/blob/2c7cfbfe/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/Dispatcher.java
----------------------------------------------------------------------
diff --git a/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/Dispatcher.java b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/Dispatcher.java
new file mode 100644
index 0000000..82c67c1
--- /dev/null
+++ b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/Dispatcher.java
@@ -0,0 +1,484 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.scimpi.dispatcher;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+import java.util.TimeZone;
+
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.isis.applib.Identifier;
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.commons.config.ConfigurationConstants;
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.exceptions.IsisException;
+import org.apache.isis.core.commons.factory.InstanceUtil;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
+import org.apache.isis.core.metamodel.facets.actcoll.typeof.TypeOfFacet;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
+import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager;
+import org.apache.isis.viewer.scimpi.dispatcher.action.ActionAction;
+import org.apache.isis.viewer.scimpi.dispatcher.context.RequestContext;
+import org.apache.isis.viewer.scimpi.dispatcher.context.RequestContext.Debug;
+import org.apache.isis.viewer.scimpi.dispatcher.context.RequestContext.Scope;
+import org.apache.isis.viewer.scimpi.dispatcher.debug.DebugAction;
+import org.apache.isis.viewer.scimpi.dispatcher.debug.DebugHtmlWriter;
+import org.apache.isis.viewer.scimpi.dispatcher.debug.DebugUserAction;
+import org.apache.isis.viewer.scimpi.dispatcher.debug.DebugUsers;
+import org.apache.isis.viewer.scimpi.dispatcher.debug.LogAction;
+import org.apache.isis.viewer.scimpi.dispatcher.edit.EditAction;
+import org.apache.isis.viewer.scimpi.dispatcher.edit.RemoveAction;
+import org.apache.isis.viewer.scimpi.dispatcher.logon.LogonAction;
+import org.apache.isis.viewer.scimpi.dispatcher.logon.LogoutAction;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.Encoder;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.HtmlFileParser;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.ProcessorLookup;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.Request;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.SimpleEncoder;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.TagProcessingException;
+import org.apache.isis.viewer.scimpi.dispatcher.util.MethodsUtils;
+import org.apache.isis.viewer.scimpi.dispatcher.view.Snippet;
+
+public class Dispatcher {
+    private static final String SHOW_UNSHOWN_MESSAGES = ConfigurationConstants.ROOT + "scimpi.show-unshown-messages";
+    public static final String ACTION = "_action";
+    public static final String EDIT = "_edit";
+    public static final String REMOVE = "_remove";
+    public static final String GENERIC = "_generic";
+    public static final String EXTENSION = "shtml";
+    private static final Logger LOG = LoggerFactory.getLogger(Dispatcher.class);
+    public static final String COMMAND_ROOT = ".app";
+    private final Map<String, Action> actions = new HashMap<String, Action>();
+    private final Map<String, String> parameters = new HashMap<String, String>();
+    private final ProcessorLookup processors = new ProcessorLookup();
+    private final HtmlFileParser parser = new HtmlFileParser(processors);
+    private final Encoder encoder = new SimpleEncoder();
+    private boolean showUnshownMessages;
+
+    public void process(final RequestContext context, final String servletPath) {
+        LOG.debug("processing request " + servletPath);
+        final AuthenticationSession session = UserManager.startRequest(context);
+        LOG.debug("exsiting session: " + session);
+        
+        String language = (String) context.getVariable("user-language");
+        if (language != null) {
+            Locale locale = Util.locale(language);
+            TimeZone timeZone = Util.timeZone((String) context.getVariable("user-time-zone"));
+            // IsisContext.getUserProfile().setLocalization(new UserLocalization(locale, timeZone));
+         } 
+        
+        IsisContext.getPersistenceSession().getTransactionManager().startTransaction();
+        context.setRequestPath(servletPath);
+        context.startRequest();
+
+        try {
+            processActions(context, false, servletPath);
+            processTheView(context);
+        } catch (final ScimpiNotFoundException e) {
+            if (context.isInternalRequest()) {
+                LOG.error("invalid page request (from within application): " + e.getMessage());
+                ErrorCollator error = new ErrorCollator(); 
+                error.missingFile("Failed to find page " + servletPath + "."); 
+                show500ErrorPage(context, e, error);             
+            } else {
+                LOG.info("invalid page request (from outside application): " + e.getMessage());
+                show404ErrorPage(context, servletPath); 
+            }
+        } catch (final NotLoggedInException e) {
+            redirectToLoginPage(context); 
+        } catch (final Throwable e) {
+            ErrorCollator error = new ErrorCollator();
+            final PersistenceSession checkSession = IsisContext.getPersistenceSession();
+            final IsisTransactionManager transactionManager = checkSession.getTransactionManager();
+            if (transactionManager.getTransaction() != null && transactionManager.getTransaction().getState().canAbort()) {
+                transactionManager.abortTransaction();
+                transactionManager.startTransaction();
+            }
+
+            final Throwable ex = e instanceof TagProcessingException ? e.getCause() : e;
+            if (ex instanceof ForbiddenException) {
+                LOG.error("invalid access to " + servletPath, e);
+                show403ErrorPage(context, error, e, ex);
+            } else {
+                LOG.error("error procesing " + servletPath, e);
+                if (context.getErrorMessage() != null) {
+                    fallbackToSimpleErrorPage(context, e);
+                } else {
+                    show500ErrorPage(context, e, error);
+                }
+            }
+        } finally {
+            try {
+                UserManager.endRequest(context.getSession());
+            } catch (final Exception e1) {
+                LOG.error("endRequest call failed", e1);
+            }
+        }
+    }
+
+    private void redirectToLoginPage(final RequestContext context) {
+        IsisContext.getMessageBroker().addWarning(
+            "You are not currently logged in! Please log in so you can continue.");
+        context.setRequestPath("/login.shtml");
+        try {
+            processTheView(context);
+        } catch (final IOException e1) {
+            throw new ScimpiException(e1);
+        }
+    }
+
+    private void show404ErrorPage(final RequestContext context, final String servletPath) {
+        ErrorCollator error = new ErrorCollator();
+        error.missingFile("Failed to find page " + servletPath + ".");
+        context.raiseError(404, error);
+    }
+
+    private void show403ErrorPage(final RequestContext context, ErrorCollator error, final Throwable e, final Throwable ex) {
+        DebugBuilder debug = error.getDebug();
+        error.message(e);
+        error.message(ex);
+        
+        final List<String> roles = ((ForbiddenException) ex).getRoles();
+        final StringBuffer roleList = new StringBuffer();
+        for (final String role : roles) {
+            if (roleList.length() > 0) {
+                roleList.append("|");
+            }
+            roleList.append(role);
+        }
+        final Identifier identifier =  ((ForbiddenException) ex).getIdentifier(); 
+        if (identifier != null) {
+            debug.appendln("Class", identifier.toClassIdentityString() + ":" + roleList);
+            debug.appendln("Member",identifier.toClassAndNameIdentityString() + ":" + roleList); 
+            debug.appendln("Other",identifier.toFullIdentityString() + ":" + roleList); 
+        }
+        
+        error.compileError(context);
+        context.raiseError(403, error);
+    }
+
+    private void show500ErrorPage(final RequestContext context, final Throwable e, ErrorCollator error) {
+        error.exception(e);
+        error.compileError(context);
+        context.raiseError(500, error);
+    }
+
+    private void fallbackToSimpleErrorPage(final RequestContext context, final Throwable e) {
+        context.setContentType("text/html");
+        final PrintWriter writer = context.getWriter();
+        writer.write("<html><head><title>Error</title></head>");
+        writer.write("<body><h1>Error</h1>");
+        writer.write("<p>Error while processing error</p><pre>");
+        e.printStackTrace(writer);
+        writer.write("</pre></body></html>");
+        writer.close();
+        LOG.error("Error while processing error", e);
+    }
+
+    protected void processTheView(final RequestContext context) throws IOException {
+        IsisTransactionManager transactionManager = IsisContext.getPersistenceSession().getTransactionManager();
+        if (transactionManager.getTransaction().getState().canFlush()) {
+            transactionManager.flushTransaction();
+        }
+        processView(context);
+        // Note - the session will have changed since the earlier call if a user
+        // has logged in or out in the action
+        // processing above.
+        transactionManager = IsisContext.getPersistenceSession().getTransactionManager();
+        if (transactionManager.getTransaction().getState().canCommit()) {
+            IsisContext.getPersistenceSession().getTransactionManager().endTransaction();
+        }
+
+        context.endRequest();
+        UserManager.endRequest(context.getSession());
+    }
+
+    public void addParameter(final String name, final String value) {
+        parameters.put(name, value);
+    }
+
+    private String getParameter(final String name) {
+        return parameters.get(name);
+    }
+
+    private void processActions(final RequestContext context, final boolean userLoggedIn, final String actionName) throws IOException {
+        if (actionName.endsWith(COMMAND_ROOT)) {
+            final int pos = actionName.lastIndexOf('/');
+            final Action action = actions.get(actionName.substring(pos, actionName.length() - COMMAND_ROOT.length()));
+            if (action == null) {
+                throw new ScimpiException("No logic for " + actionName);
+            }
+
+            LOG.debug("processing action: " + action);
+            action.process(context);
+            final String fowardTo = context.forwardTo();
+            if (fowardTo != null) {
+                processActions(context, true, fowardTo);
+            }
+        }
+    }
+
+    private void processView(final RequestContext context) throws IOException {
+        String file = context.getRequestedFile();
+        if (file == null) {
+            LOG.warn("No view specified to process");
+            return;
+        }
+        if (file.endsWith(COMMAND_ROOT)) {
+            return;
+        }
+        file = determineFile(context, file);
+        final String fullPath = context.requestedFilePath(file);
+        LOG.debug("processing file " + fullPath);
+        context.setResourcePath(fullPath);
+
+        context.setContentType("text/html");
+
+        context.addVariable("title", "Untitled Page", Scope.REQUEST);
+        final Stack<Snippet> tags = loadPageTemplate(context, fullPath);
+        final Request request = new Request(file, context, encoder, tags, processors);
+        request.appendDebug("processing " + fullPath);
+        try {
+            request.processNextTag();
+            noteIfMessagesHaveNotBeenDisplay(context);
+        } catch (final RuntimeException e) {
+            IsisContext.getMessageBroker().getMessages();
+            IsisContext.getMessageBroker().getWarnings();
+            throw e;
+        }
+        final String page = request.popBuffer();
+        final PrintWriter writer = context.getWriter();
+        writer.write(page);
+        if (context.getDebug() == Debug.PAGE) {
+            final DebugHtmlWriter view = new DebugHtmlWriter(writer, false);
+            context.append(view);
+        }
+    }
+
+    public void noteIfMessagesHaveNotBeenDisplay(final RequestContext context) {
+        final List<String> messages = IsisContext.getMessageBroker().getMessages();
+        if (showUnshownMessages) {
+            if (messages.size() > 0) {
+                context.getWriter().println("<ol class=\"messages forced\">");
+                for (String message : messages) {
+                    context.getWriter().println("<li>" + message + "</li>");                
+                }
+                context.getWriter().println("</ol>");
+            }
+            final List<String> warnings = IsisContext.getMessageBroker().getWarnings();
+            if (warnings.size() > 0) {
+                context.getWriter().println("<ol class=\"warnings forced\">");
+                for (String message : warnings) {
+                    context.getWriter().println("<li>" + message + "</li>");                
+                }
+                context.getWriter().println("</ol>");
+            }
+        }
+    }
+
+    private String determineFile(final RequestContext context, String file) {
+        final String fileName = file.trim();
+        if (fileName.startsWith(GENERIC)) {
+            final Object result = context.getVariable(RequestContext.RESULT);
+            final ObjectAdapter mappedObject = MethodsUtils.findObject(context, (String) result);
+            if (mappedObject == null) {
+                throw new ScimpiException("No object mapping for " + result);
+            }
+            if (fileName.equals(GENERIC + "." + EXTENSION)) {
+                final Facet facet = mappedObject.getSpecification().getFacet(CollectionFacet.class);
+                if (facet != null) {
+                    final ObjectSpecification specification = mappedObject.getSpecification();
+                    final TypeOfFacet facet2 = specification.getFacet(TypeOfFacet.class);
+                    file = findFileForSpecification(context, facet2.valueSpec(), "collection", EXTENSION);
+                } else {
+                    final ObjectAdapter mappedObject2 = mappedObject;
+                    if (mappedObject2.isTransient()) {
+                        file = findFileForSpecification(context, mappedObject.getSpecification(), "edit", EXTENSION);
+                    } else {
+                        file = findFileForSpecification(context, mappedObject.getSpecification(), "object", EXTENSION);
+                    }
+                }
+            } else if (fileName.equals(GENERIC + EDIT + "." + EXTENSION)) {
+                file = findFileForSpecification(context, mappedObject.getSpecification(), "edit", EXTENSION);
+            } else if (fileName.equals(GENERIC + ACTION + "." + EXTENSION)) {
+                final String method = context.getParameter("method");
+                file = findFileForSpecification(context, mappedObject.getSpecification(), method, "action", EXTENSION);
+            }
+        }
+        return file;
+    }
+
+    private String findFileForSpecification(final RequestContext context, final ObjectSpecification specification, final String name, final String extension) {
+        return findFileForSpecification(context, specification, name, name, extension);
+    }
+
+    private String findFileForSpecification(final RequestContext context, final ObjectSpecification specification, final String name, final String defaultName, final String extension) {
+
+        String find = findFile(context, specification, name, extension);
+        if (find == null) {
+            find = "/generic/" + defaultName + "." + extension;
+        }
+        return find;
+    }
+
+    private String findFile(final RequestContext context, final ObjectSpecification specification, final String name, final String extension) {
+        final String className = specification.getShortIdentifier();
+        String fileName = context.findFile("/" + className + "/" + name + "." + extension);
+        if (fileName == null) {
+            final List<ObjectSpecification> interfaces = specification.interfaces();
+            for (int i = 0; i < interfaces.size(); i++) {
+                fileName = findFile(context, interfaces.get(i), name, extension);
+                if (fileName != null) {
+                    return fileName;
+                }
+            }
+            if (specification.superclass() != null) {
+                fileName = findFile(context, specification.superclass(), name, extension);
+            }
+        }
+        return fileName;
+    }
+
+    private Stack<Snippet> loadPageTemplate(final RequestContext context, final String path) throws IOException, FileNotFoundException {
+        // TODO cache stacks and check for them first
+        copyParametersToVariableList(context);
+        LOG.debug("parsing source " + path);
+        return parser.parseHtmlFile(path, context);
+    }
+
+    private void copyParametersToVariableList(final RequestContext context) {
+        /*
+         * Enumeration parameterNames = context.getParameterNames(); while
+         * (parameterNames.hasMoreElements()) { String name = (String)
+         * parameterNames.nextElement(); if (!name.equals("view")) {
+         * context.addVariable(name, context.getParameter(name), Scope.REQUEST);
+         * } }
+         */
+    }
+
+    public void init(final String dir, final DebugUsers debugUsers) {
+        addAction(new ActionAction());
+
+        // TODO remove
+        addAction(new DebugAction(this));
+        addAction(new DebugUserAction(debugUsers));
+        addAction(new EditAction());
+        addAction(new RemoveAction());
+        addAction(new LogonAction());
+        addAction(new LogoutAction());
+        addAction(new LogAction());
+
+        final String configFile = getParameter("config");
+        if (configFile != null) {
+            final File file = new File(dir, configFile);
+            if (file.exists()) {
+                loadConfigFile(file);
+            } else {
+                throw new ScimpiException("Configuration file not found: " + configFile);
+            }
+        }
+
+        processors.init();
+        processors.addElementProcessor(new org.apache.isis.viewer.scimpi.dispatcher.view.debug.Debug(this));
+        
+        showUnshownMessages = IsisContext.getConfiguration().getBoolean(SHOW_UNSHOWN_MESSAGES, true);
+    }
+
+    private void loadConfigFile(final File file) {
+        try {
+            Document document;
+            final SAXReader reader = new SAXReader();
+            document = reader.read(file);
+            final Element root = document.getRootElement();
+            for (final Iterator i = root.elementIterator(); i.hasNext();) {
+                final Element element = (Element) i.next();
+
+                if (element.getName().equals("actions")) {
+                    for (final Iterator actions = element.elementIterator("action"); actions.hasNext();) {
+                        final Element action = (Element) actions.next();
+                        final String className = action.getText();
+                        final Action instance = (Action) InstanceUtil.createInstance(className);
+                        addAction(instance);
+                    }
+                }
+
+                if (element.getName().equals("processors")) {
+                    for (final Iterator processors = element.elementIterator("processor"); processors.hasNext();) {
+                        final Element processor = (Element) processors.next();
+                        final String className = processor.getText();
+                        final ElementProcessor instance = (ElementProcessor) InstanceUtil.createInstance(className);
+                        this.processors.addElementProcessor(instance);
+                    }
+                }
+
+            }
+        } catch (final DocumentException e) {
+            throw new IsisException(e);
+        }
+
+    }
+
+    private void addAction(final Action action) {
+        actions.put("/" + action.getName(), action);
+        action.init();
+    }
+
+    public void debug(final DebugBuilder debug) {
+        debug.startSection("Actions");
+        final Set<String> keySet = actions.keySet();
+        final ArrayList<String> list = new ArrayList<String>(keySet);
+        Collections.sort(list);
+        for (final String name : list) {
+            debug.appendln(name, actions.get(name));
+        }
+        /*
+         * new ArrayList<E>(actions.keySet().iterator()) Iterator<String> names
+         * = Collections.sort().iterator(); while (names.hasNext()) { String
+         * name = names.next(); view.appendRow(name, actions.get(name)); }
+         */
+        final Iterator<Action> iterator = actions.values().iterator();
+        while (iterator.hasNext()) {
+            iterator.next().debug(debug);
+        }
+
+        processors.debug(debug);
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/2c7cfbfe/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ElementContentProcessor.java
----------------------------------------------------------------------
diff --git a/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ElementContentProcessor.java b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ElementContentProcessor.java
new file mode 100644
index 0000000..eaae2ac
--- /dev/null
+++ b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ElementContentProcessor.java
@@ -0,0 +1,24 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.scimpi.dispatcher;
+
+public interface ElementContentProcessor extends ElementProcessor {
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/2c7cfbfe/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ElementProcessor.java
----------------------------------------------------------------------
diff --git a/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ElementProcessor.java b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ElementProcessor.java
new file mode 100644
index 0000000..471ac00
--- /dev/null
+++ b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ElementProcessor.java
@@ -0,0 +1,30 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.scimpi.dispatcher;
+
+import org.apache.isis.viewer.scimpi.dispatcher.processor.Request;
+
+public interface ElementProcessor {
+
+    String getName();
+
+    void process(Request request);
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/2c7cfbfe/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ErrorCollator.java
----------------------------------------------------------------------
diff --git a/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ErrorCollator.java b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ErrorCollator.java
new file mode 100644
index 0000000..97e297e
--- /dev/null
+++ b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ErrorCollator.java
@@ -0,0 +1,148 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.scimpi.dispatcher;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintWriter;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.isis.core.commons.config.ConfigurationConstants;
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.debug.DebugHtmlString;
+import org.apache.isis.core.commons.debug.DebugString;
+import org.apache.isis.core.commons.debug.DebugTee;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.scimpi.dispatcher.context.RequestContext;
+import org.apache.isis.viewer.scimpi.dispatcher.debug.DebugHtmlWriter;
+
+public class ErrorCollator {
+    private static final Logger LOG = LoggerFactory.getLogger(ErrorCollator.class);
+
+    private String errorRef;
+    private String message;
+    private final DebugString debugText = new DebugString();
+    private final DebugHtmlString debugHtml = new DebugHtmlString();
+    private final DebugBuilder debug = new DebugTee(debugText, debugHtml);
+ 
+    public void missingFile(String message) {
+        this.message = message;
+    }
+
+    public void message(final Throwable exception) {
+        LOG.debug(exception.getMessage(), exception);
+        message = exception.getMessage();
+        debug.appendPreformatted(message);
+    }
+
+    public void exception(final Throwable exception) {
+        String messageText = exception.getMessage(); 
+        LOG.debug(messageText, exception); 
+        try {
+            debug.startSection("Exception");
+            debug.appendException(exception);
+            debug.endSection();
+        } catch (final RuntimeException e) {
+            debug.appendln("NOTE - an exception occurred while dumping an exception!");
+            debug.appendException(e);
+        }
+        message = messageText == null ? exception.getClass().getName() : messageText; 
+    }
+        
+    public DebugBuilder getDebug() {
+        return debug;
+    }
+    
+    public void compileError(final RequestContext requestContext) {
+        errorRef = Long.toString(System.currentTimeMillis(), 36).toUpperCase();
+        LOG.info("error " + errorRef);
+
+        captureWarningsAndMessages();
+        dumpDebugDetails(requestContext);
+        writeErrorFile();
+    }
+
+    private void captureWarningsAndMessages() {
+        // Capture warnings/messages
+        if (IsisContext.getCurrentTransaction() != null) {
+            final List<String> messages = IsisContext.getMessageBroker().getMessages();
+            final List<String> warnings = IsisContext.getMessageBroker().getWarnings();
+            if (messages.size() > 0 || messages.size() > 0) {
+                debug.startSection("Warnings/Messages");
+                for (final String message : messages) {
+                    debug.appendln("message", message);
+                }
+                for (final String message : warnings) {
+                    debug.appendln("warning", message);
+                }
+            }
+        }
+    }
+
+    private void dumpDebugDetails(final RequestContext requestContext) {
+        // Dump page debug details 
+        requestContext.append(debug);
+
+        debug.startSection("Processing Trace");
+        debug.appendPreformatted(requestContext.getDebugTrace());
+        debug.endSection();
+        debug.close();
+    }
+
+    private void writeErrorFile() {
+        LOG.error(message + "\n" + debugText.toString());
+        
+        
+        PrintWriter writer;
+        try {
+            final String directory =
+                IsisContext.getConfiguration().getString(ConfigurationConstants.ROOT + "scimpi.error-snapshots", ".");
+            File dir = new File(directory);
+            if (!dir.exists()) {
+                dir.mkdirs();
+            }
+            writer = new PrintWriter(new File(dir, "error_" + errorRef + ".html"));
+            final DebugHtmlWriter writer2 = new DebugHtmlWriter(writer, true);
+            writer2.concat(debugHtml);
+            writer2.close();
+            writer.close();
+        } catch (final FileNotFoundException e) {
+            LOG.error("Failed to archive error page", e);
+        }
+    }
+
+    public String getReference() {
+        return errorRef;
+    }
+    
+    public String getDetails() {
+        return debugHtml.toString();
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    
+}
+

http://git-wip-us.apache.org/repos/asf/isis/blob/2c7cfbfe/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ForbiddenException.java
----------------------------------------------------------------------
diff --git a/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ForbiddenException.java b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ForbiddenException.java
new file mode 100644
index 0000000..32d0c0c
--- /dev/null
+++ b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ForbiddenException.java
@@ -0,0 +1,68 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.scimpi.dispatcher;
+
+import java.util.List;
+
+import org.apache.isis.applib.Identifier;
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.metamodel.facetapi.IdentifiedHolder;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+
+/**
+ * Indicates that request could not complete as it could not access (for
+ * security reasons) some of the content.
+ */
+public class ForbiddenException extends ScimpiException {
+    private static final long serialVersionUID = 1L;
+    public static final boolean VISIBLE_AND_USABLE = true;
+    public static final boolean VISIBLE = false;
+    private final Identifier identifier;
+    private final AuthenticationSession session;
+
+    public ForbiddenException(final String message) {
+        this(IsisContext.getAuthenticationSession(), message);
+    }
+
+    private ForbiddenException(final AuthenticationSession session, final String message) {
+        super(message + " for " + session.getUserName() + " " + session.getRoles());
+        this.session = session;
+        identifier = null;
+    }
+
+    public ForbiddenException(final IdentifiedHolder target, final boolean isVisibleAndUsabable) {
+        this(target.getIdentifier(), IsisContext.getAuthenticationSession(), isVisibleAndUsabable);
+    }
+
+    private ForbiddenException(final Identifier identifier, final AuthenticationSession session, final boolean isVisibleAndUsabable) {
+        super((identifier.getType() == Identifier.Type.PROPERTY_OR_COLLECTION ? "Field" : "Action") + " '" + identifier.getMemberName() + "' in " + identifier.getClassName() + " is not " + (isVisibleAndUsabable ? "visible/usable " : "visible") + " for " + session.getUserName() + " "
+                + session.getRoles());
+        this.identifier = identifier;
+        this.session = session;
+    }
+
+    public Identifier getIdentifier() {
+        return identifier;
+    }
+
+    public List<String> getRoles() {
+        return session.getRoles();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/2c7cfbfe/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/Names.java
----------------------------------------------------------------------
diff --git a/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/Names.java b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/Names.java
new file mode 100644
index 0000000..29b15e5
--- /dev/null
+++ b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/Names.java
@@ -0,0 +1,91 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.scimpi.dispatcher;
+
+public interface Names {
+    static final String PREFIX = "_logon-";
+
+    static final String BUTTON_TITLE = "button-title";
+    static final String CANCEL_TO = "cancel-to";
+    static final String COLLECTION = "collection";
+    static final String CONFIRM = "confirm";
+    static final String CLASS = "class";
+    static final String CONTAINER_CLASS = "container-" + CLASS;
+    static final String DEFAULT = "default";
+    static final String ELEMENT_NAME = "element-name";
+    static final String ELEMENT = "element";
+    static final String EVEN_ROW_CLASS = "even-row";
+    static final String FALSE = "false";
+    static final String ERROR = "error";
+    static final String FIELD = "field";
+    static final String FIELD_NAME = "field-name";
+    static final String FOOTER = "footer";
+    static final String FORM_ID = "form-id";
+    static final String FORM_TITLE = "title";
+    static final String FORMS = "show-forms";
+    static final String HEADER = "header";
+    static final String ICON_CLASS = "icon";
+    static final String HIDDEN = "hidden";
+    static final String HIDE_UNEDITABLE = "hide-uneditable";
+    static final String HEADER_LEVEL = "header";
+    static final String ID = "id";
+    static final String LABEL_DELIMITER = "label";
+    static final String LINK_VIEW = "link-view";
+    static final String LINK_NAME = "link-name";
+    static final String LINK_OBJECT = "link-object";
+    static final String METHOD = "method";
+    static final String MESSAGE = "message";
+    static final String NAME = "name";
+    static final String ODD_ROW_CLASS = "odd-row";
+    static final String OBJECT = "object";
+    static final String PARAMETER = "param";
+    static final String PARAMETER_NUMBER = "number";
+    static final String PLURAL = "plural";
+    static final String REFERENCE_NAME = "reference-name";
+    static final String RESULT_NAME = "result-name";
+    static final String RESULT_OVERRIDE = "result-override";
+    static final String ROW_CLASSES = "row-classes";
+    static final String SCOPE = "scope";
+    static final String SHOW_ICON = "icon";
+    static final String SHOW_SELECT = "select";
+    static final String SHOW_EDIT = "edit";
+    static final String SHOW_DELETE = "delete";
+    static final String SHOW_MESSAGE = "show-message";
+    static final String SHOW_TITLE = "show-title";
+    static final String TRUNCATE = "truncate";
+    static final String TABLE_TITLE = "title";
+    static final String TYPE = "type";
+    static final String VIEW = "view";
+    static final String VALUE = "value";
+    static final String VERSION = "version";
+    static final String USER = "user";
+    static final String VOID = "void";
+    static final String WHEN = "when";
+    static final String ENTRY_FIELDS = "entry-fields";
+    
+    
+    static final String LOGON_OBJECT = PREFIX + OBJECT;
+    static final String LOGON_METHOD = PREFIX + METHOD;
+    static final String LOGON_SCOPE = PREFIX + SCOPE;
+    static final String LOGON_RESULT_NAME = PREFIX + RESULT_NAME;
+    static final String LOGON_FORM_ID = PREFIX + "form-id";
+    
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/2c7cfbfe/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/NotLoggedInException.java
----------------------------------------------------------------------
diff --git a/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/NotLoggedInException.java b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/NotLoggedInException.java
new file mode 100644
index 0000000..9cd54a3
--- /dev/null
+++ b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/NotLoggedInException.java
@@ -0,0 +1,31 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.scimpi.dispatcher;
+
+/**
+ * Indicates that request could not complete as a user was not logged in.
+ */
+public class NotLoggedInException extends ScimpiException {
+    private static final long serialVersionUID = 1L;
+
+    public NotLoggedInException() {
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/2c7cfbfe/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ResolveFieldUtil.java
----------------------------------------------------------------------
diff --git a/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ResolveFieldUtil.java b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ResolveFieldUtil.java
new file mode 100644
index 0000000..a0ae5cf
--- /dev/null
+++ b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ResolveFieldUtil.java
@@ -0,0 +1,71 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.viewer.scimpi.dispatcher;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+
+import static org.apache.isis.core.commons.ensure.Ensure.ensureThatState;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+
+public final class ResolveFieldUtil {
+
+    private ResolveFieldUtil(){}
+
+    /**
+     * Walking the graph.
+     *
+     * <p>
+     *     This combines the implementations of both the DN Objectstore
+     *     and also the in-memory objectstore.
+     * </p>
+     */
+    public static void resolveField(final ObjectAdapter object, final ObjectAssociation association) {
+
+
+        // DN impl.
+        {
+            final ObjectAdapter referencedCollectionAdapter = association.get(object);
+
+            // this code originally brought in from the JPA impl, but seems reasonable.
+            if (association.isOneToManyAssociation()) {
+                ensureThatState(referencedCollectionAdapter, is(notNullValue()));
+
+                final Object referencedCollection = referencedCollectionAdapter.getObject();
+                ensureThatState(referencedCollection, is(notNullValue()));
+
+                // if a proxy collection, then force it to initialize.  just 'touching' the object is sufficient.
+                // REVIEW: I wonder if this is actually needed; does JDO use proxy collections?
+                referencedCollection.hashCode();
+            }
+
+            // the JPA impl used to also call its lifecycle listener on the referenced collection object, eg List,
+            // itself.  I don't think this makes sense to do for JDO (the collection is not a PersistenceCapable).
+        }
+
+        // In-memory objectstore impl
+        {
+            final ObjectAdapter referenceAdapter = association.get(object);
+            referenceAdapter.markAsResolvedIfPossible();
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/2c7cfbfe/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ScimpiException.java
----------------------------------------------------------------------
diff --git a/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ScimpiException.java b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ScimpiException.java
new file mode 100644
index 0000000..18e7646
--- /dev/null
+++ b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ScimpiException.java
@@ -0,0 +1,44 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.scimpi.dispatcher;
+
+public class ScimpiException extends RuntimeException {
+    private static final long serialVersionUID = 1L;
+
+    public ScimpiException() {
+    }
+
+    public ScimpiException(final String message) {
+        super(message);
+    }
+
+    public ScimpiException(final Throwable cause) {
+        super(cause);
+    }
+
+    public ScimpiException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+    public String getHtmlMessage() {
+        return getMessage();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/2c7cfbfe/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ScimpiNotFoundException.java
----------------------------------------------------------------------
diff --git a/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ScimpiNotFoundException.java b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ScimpiNotFoundException.java
new file mode 100644
index 0000000..6b1e77a
--- /dev/null
+++ b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/ScimpiNotFoundException.java
@@ -0,0 +1,41 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.scimpi.dispatcher;
+
+public class ScimpiNotFoundException extends ScimpiException {
+    public ScimpiNotFoundException() {
+        super();
+    }
+
+    public ScimpiNotFoundException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+    public ScimpiNotFoundException(final String message) {
+        super(message);
+    }
+
+    public ScimpiNotFoundException(final Throwable cause) {
+        super(cause);
+    }
+
+    private static final long serialVersionUID = 1L;
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/2c7cfbfe/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/TagOrderException.java
----------------------------------------------------------------------
diff --git a/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/TagOrderException.java b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/TagOrderException.java
new file mode 100644
index 0000000..97e00e4
--- /dev/null
+++ b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/TagOrderException.java
@@ -0,0 +1,31 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.scimpi.dispatcher;
+
+import org.apache.isis.viewer.scimpi.dispatcher.processor.Request;
+
+public class TagOrderException extends ScimpiException {
+    private static final long serialVersionUID = 1L;
+
+    public TagOrderException(final Request request) {
+        super("Invalid tag in this context: " + request.getTag().getName());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/2c7cfbfe/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/UserManager.java
----------------------------------------------------------------------
diff --git a/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/UserManager.java b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/UserManager.java
new file mode 100644
index 0000000..cdb1b04
--- /dev/null
+++ b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/UserManager.java
@@ -0,0 +1,91 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.scimpi.dispatcher;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.isis.core.commons.authentication.AnonymousSession;
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.runtime.authentication.AuthenticationManager;
+import org.apache.isis.core.runtime.authentication.AuthenticationRequestPassword;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.scimpi.dispatcher.context.RequestContext;
+
+public class UserManager {
+
+    private static final Logger LOG = LoggerFactory.getLogger(UserManager.class);
+    private static UserManager instance;
+
+    private static AuthenticationManager getAuthenticationManager() {
+        if (instance == null) {
+            throw new IllegalStateException("Server initialisation failed, or not defined as a context listener");
+        }
+        return instance.authenticationManager;
+    }
+
+    public static AuthenticationSession startRequest(final RequestContext context) {
+        AuthenticationSession session = context.getSession();
+        if (session == null) {
+            session = new AnonymousSession();
+            LOG.debug("start anonymous request: " + session);
+        } else {
+            LOG.debug("start request for: " + session.getUserName());
+        }
+        IsisContext.closeSession();
+        IsisContext.openSession(session);
+        return session;
+    }
+
+    public static AuthenticationSession authenticate(final AuthenticationRequestPassword passwordAuthenticationRequest) {
+        final AuthenticationSession session = getAuthenticationManager().authenticate(passwordAuthenticationRequest);
+        if (session != null) {
+            LOG.info("log on user " + session.getUserName());
+            IsisContext.closeSession();
+            IsisContext.openSession(session);
+        }
+        return session;
+    }
+
+    public static void endRequest(final AuthenticationSession session) {
+        if (session == null) {
+            LOG.debug("end anonymous request");
+        } else {
+            LOG.debug("end request for: " + session.getUserName());
+        }
+        IsisContext.closeSession();
+    }
+
+    public static void logoffUser(final AuthenticationSession session) {
+        LOG.info("log off user " + session.getUserName());
+        IsisContext.closeSession();
+        getAuthenticationManager().closeSession(session);
+
+        final AnonymousSession replacementSession = new AnonymousSession();
+        IsisContext.openSession(replacementSession);
+    }
+
+    private final AuthenticationManager authenticationManager;
+
+    public UserManager(final AuthenticationManager authenticationManager) {
+        this.authenticationManager = authenticationManager;
+        UserManager.instance = this;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/2c7cfbfe/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/Util.java
----------------------------------------------------------------------
diff --git a/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/Util.java b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/Util.java
new file mode 100644
index 0000000..b485dd0
--- /dev/null
+++ b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/Util.java
@@ -0,0 +1,105 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.scimpi.dispatcher;
+
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+class Util {
+    
+    public static final String DEFAULT_TIME_ZONE = "Europe/London";
+    public static final String DEFAULT_LANGUAGE = "English, United Kingdom (en-gb)";
+    
+
+    private Util() {}
+
+    public static boolean hasChanged(String version1, String version2) {
+        return version2 == null && version1 != null || (version2 != null && !version2.equals(version1));
+    }
+
+    public static List<String> languages() {
+        Locale[] locales = DateFormat.getAvailableLocales();
+        List<String> list = new ArrayList<String>(locales.length);
+        for (Locale locale : locales) {
+            list.add(localeName(locale));
+        }
+        Collections.sort(list);
+        return list;
+    }
+    
+    public static List<String> timeZones() {
+        List<String> timezones = Arrays.asList(TimeZone.getAvailableIDs());
+        Collections.sort(timezones);
+        return timezones;
+    }
+
+    public static TimeZone timeZone(String timeZoneEntry) {
+        TimeZone timeZone = TimeZone.getTimeZone(timeZoneEntry);
+        return timeZone;
+    }
+
+    public static Locale locale(String localeCode) {
+        String substring[] = localeCode.trim().split("-");
+        Locale locale;
+        switch (substring.length) {
+        case 1:
+            locale = new Locale(substring[0]);                    
+            break;
+        case 2:
+            locale = new Locale(substring[0], substring[1]);                    
+            break;
+        case 3:
+            locale = new Locale(substring[0], substring[1], substring[3]);                    
+            break;
+        default:
+            locale = Locale.getDefault();
+            break;
+        }
+        return locale;
+    }
+
+    public static String languageName(String languageCode) {
+        Locale locale = locale(languageCode);
+        return localeName(locale);
+    }
+
+    public static String codeForLanguage(String language) {
+        Locale[] locales = DateFormat.getAvailableLocales();
+        for (Locale locale : locales) {
+            String name = localeName(locale);
+            if (name.equals(language)) {
+                return locale.toString().toLowerCase().replace('_', '-');
+            }
+        }
+        return null;
+    }
+
+    public static String localeName(Locale locale) {
+        String language = locale.getDisplayLanguage();
+        String country = locale.getDisplayCountry().length() == 0 ? "" :  ", " + (locale.getDisplayCountry());
+        return language + country + " (" +  locale.toString().toLowerCase().replace('_', '-') + ")";
+    }
+   
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/2c7cfbfe/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/action/ActionAction.java
----------------------------------------------------------------------
diff --git a/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/action/ActionAction.java b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/action/ActionAction.java
new file mode 100644
index 0000000..44a9756
--- /dev/null
+++ b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/action/ActionAction.java
@@ -0,0 +1,268 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.scimpi.dispatcher.action;
+
+import java.io.IOException;
+import java.util.List;
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.applib.profiles.Localization;
+import org.apache.isis.core.commons.authentication.AnonymousSession;
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.commons.authentication.MessageBroker;
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
+import org.apache.isis.core.metamodel.adapter.version.ConcurrencyException;
+import org.apache.isis.core.metamodel.adapter.version.Version;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.consent.Veto;
+import org.apache.isis.core.metamodel.facets.object.parseable.ParseableFacet;
+import org.apache.isis.core.metamodel.facets.object.parseable.TextEntryParseException;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.scimpi.dispatcher.Action;
+import org.apache.isis.viewer.scimpi.dispatcher.Dispatcher;
+import org.apache.isis.viewer.scimpi.dispatcher.context.RequestContext;
+import org.apache.isis.viewer.scimpi.dispatcher.context.RequestContext.Scope;
+import org.apache.isis.viewer.scimpi.dispatcher.edit.FieldEditState;
+import org.apache.isis.viewer.scimpi.dispatcher.edit.FormState;
+import org.apache.isis.viewer.scimpi.dispatcher.util.MethodsUtils;
+
+public class ActionAction implements Action {
+
+    public static final String ACTION = "action";
+
+    // REVIEW: should provide this rendering context, rather than hardcoding.
+    // the net effect currently is that class members annotated with 
+    // @Hidden(where=Where.ANYWHERE) or @Disabled(where=Where.ANYWHERE) will indeed
+    // be hidden/disabled, but will be visible/enabled (perhaps incorrectly) 
+    // for any other value for Where
+    private final Where where = Where.ANYWHERE;
+
+    @Override
+    public String getName() {
+        return ACTION;
+    }
+
+    /**
+     * REVIEW - this and EditAction are very similar - refactor out common code.
+     */
+    @Override
+    public void process(final RequestContext context) throws IOException {
+        final String objectId = context.getParameter("_" + OBJECT);
+        final String version = context.getParameter("_" + VERSION);
+        final String formId = context.getParameter("_" + FORM_ID);
+        final String methodName = context.getParameter("_" + METHOD);
+        final String override = context.getParameter("_" + RESULT_OVERRIDE);
+        String resultName = context.getParameter("_" + RESULT_NAME);
+        final String message = context.getParameter("_" + MESSAGE);
+        resultName = resultName == null ? RequestContext.RESULT : resultName;
+
+        FormState entryState = null;
+        try {
+            final ObjectAdapter object = MethodsUtils.findObject(context, objectId);
+            // FIXME need to find method based on the set of parameters.
+            // otherwise overloaded method may be incorrectly
+            // selected.
+            final ObjectAction action = MethodsUtils.findAction(object, methodName);
+            entryState = validateParameters(context, action, object);
+
+            AuthenticationSession session = context.getSession();
+            if (session == null && action.isVisible(new AnonymousSession(), object, where).isVetoed()) {
+                session = new AnonymousSession();
+            }
+
+            final Version originalVersion = context.getVersion(version);
+            object.checkLock(originalVersion);
+            if (entryState.isValid()) {
+                final boolean hasResult = invokeMethod(context, resultName, object, action, entryState);
+                String view = context.getParameter(hasResult ? "_" + VIEW : "_" + VOID);
+
+                final int questionMark = view == null ? -1 : view.indexOf("?");
+                if (questionMark > -1) {
+                    final String params[] = view.substring(questionMark + 1).split("&");
+                    for (final String param : params) {
+                        final int equals = param.indexOf("=");
+                        context.addVariable(param.substring(0, equals), param.substring(equals + 1), Scope.REQUEST);
+                        view = view.substring(0, questionMark);
+                    }
+                }
+                context.setRequestPath(view);
+                if (message != null) {
+                    final MessageBroker messageBroker = getMessageBroker();
+                    messageBroker.addMessage(message);
+                }
+                if (override != null) {
+                    context.addVariable(resultName, override, Scope.REQUEST);
+                }
+                if (!action.hasReturn() && context.getVariable(resultName) == null) {
+                    context.addVariable(resultName, objectId, Scope.REQUEST);
+                }
+            } else {
+                entryState.setForm(formId);
+                context.addVariable(ENTRY_FIELDS, entryState, Scope.REQUEST);
+                context.addVariable(resultName, objectId, Scope.REQUEST);
+                if (override != null) {
+                    context.addVariable(resultName, override, Scope.REQUEST);
+                }
+                final String error = entryState.getError();
+                final String view = context.getParameter("_" + ERROR);
+                context.setRequestPath(view, Dispatcher.ACTION);
+
+                final MessageBroker messageBroker = getMessageBroker();
+                messageBroker.addWarning(error);
+            }
+        } catch (final ConcurrencyException e) {
+            final ObjectAdapter adapter = getAdapterManager().getAdapterFor(e.getOid()); 
+            String user = adapter.getOid().getVersion().getUser();
+            String errorMessage = "The data for '" + adapter.titleString() + "' was changed by " + user
+                    + ". Please repeat the action based on those changes.";
+            getMessageBroker().addMessage(errorMessage);
+
+            entryState.setForm(formId);
+            context.addVariable(ENTRY_FIELDS, entryState, Scope.REQUEST);
+            context.addVariable(resultName, objectId, Scope.REQUEST);
+            if (override != null) {
+                context.addVariable(resultName, override, Scope.REQUEST);
+            }
+            final String error = entryState.getError();
+            if (error != null) {
+                context.addVariable(RequestContext.ERROR, error, Scope.REQUEST);
+            }
+
+            final String view = context.getParameter("_" + ERROR);
+            context.setRequestPath(view, Dispatcher.ACTION);
+
+        } catch (final RuntimeException e) {
+            getMessageBroker().getMessages();
+            getMessageBroker().getWarnings();
+            throw e;
+        }
+    }
+
+    private boolean invokeMethod(final RequestContext context, final String variable, final ObjectAdapter object, final ObjectAction action, final FormState entryState) {
+
+        final ObjectAdapter[] parameters = getParameters(action, entryState);
+        final String scopeName = context.getParameter("_" + SCOPE);
+        final Scope scope = RequestContext.scope(scopeName, Scope.REQUEST);
+        return MethodsUtils.runMethod(context, action, object, parameters, variable, scope);
+    }
+
+    private ObjectAdapter[] getParameters(final ObjectAction action, final FormState entryState) {
+        final int parameterCount = action.getParameterCount();
+        final ObjectAdapter[] parameters = new ObjectAdapter[parameterCount];
+        for (int i = 0; i < parameterCount; i++) {
+            parameters[i] = entryState.getField(parameterName(i)).getValue();
+        }
+        return parameters;
+    }
+
+    private FormState validateParameters(final RequestContext context, final ObjectAction action, final ObjectAdapter object) {
+        final FormState formState = new FormState();
+        final List<ObjectActionParameter> parameters2 = action.getParameters();
+        final int parameterCount = action.getParameterCount();
+        for (int i = 0; i < parameterCount; i++) {
+            final String fieldName = parameterName(i);
+            String newEntry = context.getParameter(fieldName);
+
+            if (newEntry != null && newEntry.equals("-OTHER-")) {
+                newEntry = context.getParameter(fieldName + "-other");
+            }
+
+            if (newEntry == null) {
+                // TODO figure out a better way to determine if boolean or a
+                // password
+                final ObjectSpecification spec = parameters2.get(i).getSpecification();
+                if (spec.isOfType(IsisContext.getSpecificationLoader().loadSpecification(boolean.class)) || spec.isOfType(IsisContext.getSpecificationLoader().loadSpecification(Boolean.class))) {
+                    newEntry = FALSE;
+                } else {
+                    newEntry = "";
+                }
+            }
+            final FieldEditState fieldState = formState.createField(fieldName, newEntry);
+            Consent consent = null;
+
+            if (!parameters2.get(i).isOptional() && newEntry.equals("")) {
+                consent = new Veto(parameters2.get(i).getName() + " required");
+                formState.setError("Not all fields have been set");
+
+            } else if (parameters2.get(i).getSpecification().getFacet(ParseableFacet.class) != null) {
+                try {
+                    final ParseableFacet facet = parameters2.get(i).getSpecification().getFacet(ParseableFacet.class);
+                    Localization localization = IsisContext.getLocalization(); 
+                    final String message = parameters2.get(i).isValid(object, newEntry, localization); 
+                    if (message != null) {
+                        consent = new Veto(message);
+                        formState.setError("Not all fields are valid");
+                    }
+                    final ObjectAdapter entry = facet.parseTextEntry(null, newEntry, localization);
+                    fieldState.setValue(entry);
+                } catch (final TextEntryParseException e) {
+                    consent = new Veto(e.getMessage());
+                    formState.setError("Not all fields are valid");
+                }
+            } else {
+                fieldState.setValue(newEntry == null ? null : context.getMappedObject(newEntry));
+            }
+            if (consent != null && consent.isVetoed()) {
+                fieldState.setError(consent.getReason());
+            }
+        }
+
+        if (formState.isValid()) {
+            final ObjectAdapter[] parameters = getParameters(action, formState);
+            final Consent consent = action.isProposedArgumentSetValid(object, parameters);
+            if (consent != null && consent.isVetoed()) {
+                formState.setError(consent.getReason());
+            }
+        }
+
+        return formState;
+    }
+
+    public static String parameterName(final int index) {
+        return PARAMETER + (index + 1);
+    }
+
+    @Override
+    public void init() {
+    }
+
+    @Override
+    public void debug(final DebugBuilder debug) {
+    }
+    
+
+    ///////////////////////////////////////////////////////////////////////////
+    // from context
+    ///////////////////////////////////////////////////////////////////////////
+    
+    protected MessageBroker getMessageBroker() {
+        return IsisContext.getMessageBroker();
+    }
+
+    protected AdapterManager getAdapterManager() {
+        return IsisContext.getPersistenceSession().getAdapterManager();
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/2c7cfbfe/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/action/Attributes.java
----------------------------------------------------------------------
diff --git a/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/action/Attributes.java b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/action/Attributes.java
new file mode 100644
index 0000000..67d13e2
--- /dev/null
+++ b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/action/Attributes.java
@@ -0,0 +1,131 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.scimpi.dispatcher.action;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+import org.htmlparser.Attribute;
+import org.htmlparser.nodes.TagNode;
+
+import org.apache.isis.viewer.scimpi.dispatcher.context.RequestContext;
+
+public class Attributes {
+    private static final String TRUE = " true yes on ";
+    private static final String FALSE = " false no off ";
+    private final TagNode tagNode;
+    private final RequestContext context;
+
+    public Attributes(final TagNode tagNode, final RequestContext context) {
+        this.tagNode = tagNode;
+        this.context = context;
+    }
+
+    public boolean isPropertySet(final String name) {
+        final String attribute = tagNode.getAttribute(name);
+        int end = attribute.length() - 1;
+        final int pos = attribute.indexOf(':');
+        end = pos == -1 ? end : pos;
+        final String variabelName = attribute.substring(2, end);
+        final Object value = context.getVariable(variabelName);
+        return value != null;
+        // return attribute != null &&
+        // !context.replaceVariables(attribute).equals("");
+    }
+
+    public boolean isPropertySpecified(final String name) {
+        final String attribute = tagNode.getAttribute(name);
+        return attribute != null;
+    }
+
+    public String getOptionalProperty(final String name, final boolean ensureVariablesExists) {
+        return getOptionalProperty(name, null, ensureVariablesExists);
+    }
+
+    public String getOptionalProperty(final String name, final String defaultValue, final boolean ensureVariablesExists) {
+        final String attribute = tagNode.getAttribute(name);
+        return attribute == null ? defaultValue : context.replaceVariables(attribute);
+    }
+
+    public String getRequiredProperty(final String name, final boolean ensureVariablesExists) {
+        final String attribute = tagNode.getAttribute(name);
+        if (attribute == null) {
+            throw new RequiredPropertyException("Missing property: " + name);
+        } else if (attribute.equals("")) {
+            throw new RequiredPropertyException("Property not set: " + name);
+        } else {
+            return context.replaceVariables(attribute);
+        }
+    }
+
+    public String[] getPropertyNames(final String excluding[]) {
+        final Vector attributes = tagNode.getAttributesEx();
+        final String[] names = new String[attributes.size()];
+        int i = 0;
+        names: for (final Enumeration e = attributes.elements(); e.hasMoreElements();) {
+            final String name = ((Attribute) e.nextElement()).getName();
+            if (name == null) {
+                continue;
+            }
+            for (int j = 0; j < excluding.length; j++) {
+                if (name.equals(excluding[j])) {
+                    continue names;
+                }
+            }
+            if (tagNode.getAttribute(name) != null) {
+                names[i++] = name;
+            }
+        }
+
+        final String[] array = new String[i];
+        System.arraycopy(names, 0, array, 0, i);
+        return array;
+    }
+
+    @Override
+    public String toString() {
+        return tagNode.toHtml(); // getAttributesEx().toString();
+    }
+
+    public boolean isRequested(final String name) {
+        return isRequested(name, false);
+    }
+
+    public boolean isRequested(final String name, final boolean defaultValue) {
+        final String flag = getOptionalProperty(name, true);
+        if (flag == null) {
+            return defaultValue;
+        } else {
+            return isTrue(flag);
+        }
+    }
+
+    public static boolean isTrue(final String flag) {
+        final String value = " " + flag.toLowerCase().trim() + " ";
+        if (TRUE.indexOf(value) >= 0) {
+            return true;
+        } else if (FALSE.indexOf(value) >= 0) {
+            return false;
+        } else {
+            throw new PropertyException("Illegal flag value: " + flag);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/2c7cfbfe/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/action/PropertyException.java
----------------------------------------------------------------------
diff --git a/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/action/PropertyException.java b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/action/PropertyException.java
new file mode 100644
index 0000000..2e42188
--- /dev/null
+++ b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/action/PropertyException.java
@@ -0,0 +1,44 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.scimpi.dispatcher.action;
+
+import org.apache.isis.viewer.scimpi.dispatcher.ScimpiException;
+
+public class PropertyException extends ScimpiException {
+
+    private static final long serialVersionUID = 1L;
+
+    public PropertyException() {
+        super();
+    }
+
+    public PropertyException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+    public PropertyException(final String message) {
+        super(message);
+    }
+
+    public PropertyException(final Throwable cause) {
+        super(cause);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/2c7cfbfe/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/action/RequiredPropertyException.java
----------------------------------------------------------------------
diff --git a/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/action/RequiredPropertyException.java b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/action/RequiredPropertyException.java
new file mode 100644
index 0000000..20430d3
--- /dev/null
+++ b/mothballed/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/action/RequiredPropertyException.java
@@ -0,0 +1,43 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.scimpi.dispatcher.action;
+
+import org.apache.isis.viewer.scimpi.dispatcher.ScimpiException;
+
+public class RequiredPropertyException extends ScimpiException {
+    private static final long serialVersionUID = 1L;
+
+    public RequiredPropertyException() {
+        super();
+    }
+
+    public RequiredPropertyException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+    public RequiredPropertyException(final String message) {
+        super(message);
+    }
+
+    public RequiredPropertyException(final Throwable cause) {
+        super(cause);
+    }
+
+}