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/09/03 14:32:17 UTC

[26/87] [abbrv] [partial] isis git commit: ISIS-1194: moving the wicket submodules to be direct children of core; removing the isis-viewer-wicket parent pom.

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/zclip/ZeroClipboardPanel.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/zclip/ZeroClipboardPanel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/zclip/ZeroClipboardPanel.java
new file mode 100644
index 0000000..15bd4b4
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/zclip/ZeroClipboardPanel.java
@@ -0,0 +1,177 @@
+/**
+ *  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.wicket.ui.components.widgets.zclip;
+
+import de.agilecoders.wicket.jquery.util.Strings2;
+
+import org.apache.wicket.Page;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.link.AbstractLink;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.model.LoadableDetachableModel;
+import org.apache.wicket.request.Url;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.viewer.wicket.model.hints.IsisEnvelopeEvent;
+import org.apache.isis.viewer.wicket.model.hints.IsisUiHintEvent;
+import org.apache.isis.viewer.wicket.model.hints.UiHintContainer;
+import org.apache.isis.viewer.wicket.model.models.EntityModel;
+import org.apache.isis.viewer.wicket.model.models.PageType;
+import org.apache.isis.viewer.wicket.ui.pages.PageClassRegistry;
+import org.apache.isis.viewer.wicket.ui.pages.PageClassRegistryAccessor;
+import org.apache.isis.viewer.wicket.ui.panels.PanelAbstract;
+import org.apache.isis.viewer.wicket.ui.util.Links;
+
+public class ZeroClipboardPanel extends PanelAbstract<EntityModel> {
+
+    private static final long serialVersionUID = 1L;
+    
+    private static final String ID_SUBSCRIBING_LINK = "subscribingLink";
+    private static final String ID_COPY_LINK = "copyLink";
+    private static final String ID_SIMPLE_CLIPBOARD_MODAL_WINDOW = "simpleClipboardModalWindow";
+
+    private AbstractLink subscribingLink;
+    private AjaxLink<ObjectAdapter> copyLink;
+    private SimpleClipboardModalWindow simpleClipboardModalWindow;
+
+    public ZeroClipboardPanel(String id, EntityModel entityModel) {
+        super(id, entityModel);
+    }
+    
+    @Override
+    protected void onInitialize() {
+        super.onInitialize();
+
+        if(copyLink == null) {
+            copyLink = createLink(ID_COPY_LINK);
+            addOrReplace(copyLink);
+        }
+        EntityModel model = getModel();
+        addSubscribingLink(model);
+        addSimpleClipboardModalWindow();
+
+        EntityModel.RenderingHint renderingHint = model.getRenderingHint();
+        EntityModel.Mode mode = model.getMode();
+        setVisible(renderingHint == EntityModel.RenderingHint.REGULAR && mode == EntityModel.Mode.VIEW);
+    }
+
+    private AjaxLink<ObjectAdapter> createLink(String linkId) {
+        return newSimpleClipboardLink(linkId);
+    }
+
+    private AjaxLink<ObjectAdapter> newSimpleClipboardLink(String linkId) {
+        return new AjaxLink<ObjectAdapter>(linkId) {
+            private static final long serialVersionUID = 1L;
+            
+            @Override
+            public void onClick(AjaxRequestTarget target) {
+                
+                String contentId = simpleClipboardModalWindow.getContentId();
+                SimpleClipboardModalWindowPanel panel = new SimpleClipboardModalWindowPanel(contentId);
+                SimpleClipboardModalWindowForm form = new SimpleClipboardModalWindowForm("form");
+
+                final TextField<String> textField = new TextField<String>("textField", new LoadableDetachableModel<String>() {
+                    private static final long serialVersionUID = 1L;
+
+                    @SuppressWarnings({ "rawtypes", "unchecked" })
+                    @Override
+                    protected String load() {
+                        if(subscribingLink instanceof BookmarkablePageLink) {
+                            final BookmarkablePageLink<?> link = (BookmarkablePageLink<?>) subscribingLink;
+                            final Class pageClass = link.getPageClass();
+                            final PageParameters pageParameters = link.getPageParameters();
+                            final CharSequence urlFor = link.urlFor(pageClass, pageParameters);
+                            return getRequestCycle().getUrlRenderer().renderFullUrl(Url.parse(urlFor));
+                        } else {
+                            return "";
+                        }
+                    }
+                });
+                panel.add(form);
+                form.add(textField);
+                
+                textField.setOutputMarkupId(true);
+
+                CharSequence modalId = Strings2.escapeMarkupId(simpleClipboardModalWindow.getMarkupId());
+                CharSequence textFieldId = Strings2.escapeMarkupId(textField.getMarkupId());
+                target.appendJavaScript(String.format("$('#%s').one('shown.bs.modal', function(){Wicket.$('%s').select();});", modalId, textFieldId));
+
+                simpleClipboardModalWindow.setPanel(panel, target);
+                simpleClipboardModalWindow.showPrompt(target);
+                
+                target.focusComponent(textField);
+            }
+        };
+    }
+
+    
+    private void addSubscribingLink(UiHintContainer uiHintContainer) {
+        if(uiHintContainer == null && subscribingLink != null) {
+            // ignore, since has already been primed
+            return;
+        }
+        AbstractLink subscribingLink = createSubscribingLink(uiHintContainer);
+        if(subscribingLink != null) {
+            this.subscribingLink = subscribingLink;
+            this.subscribingLink.setOutputMarkupId(true);
+        }
+    }
+
+    private void addSimpleClipboardModalWindow() {
+        simpleClipboardModalWindow = SimpleClipboardModalWindow.newModalWindow(ID_SIMPLE_CLIPBOARD_MODAL_WINDOW);
+        addOrReplace(simpleClipboardModalWindow);
+    }
+
+    private AbstractLink createSubscribingLink(UiHintContainer uiHintContainer) {
+        if(uiHintContainer == null || !(uiHintContainer instanceof EntityModel)) {
+            // return a no-op
+            return null;
+        } else {
+            final EntityModel entityModel = (EntityModel) uiHintContainer;
+            final PageParameters pageParameters = entityModel.getPageParameters();
+            final Class<? extends Page> pageClass = getPageClassRegistry().getPageClass(PageType.ENTITY);
+            return Links.newBookmarkablePageLink(ID_SUBSCRIBING_LINK, pageParameters, pageClass);
+        }
+    }
+
+    // //////////////////////////////////////
+    
+    @Override
+    public void onEvent(IEvent<?> event) {
+        super.onEvent(event);
+
+        final IsisUiHintEvent uiHintEvent = IsisEnvelopeEvent.openLetter(event, IsisUiHintEvent.class);
+        if(uiHintEvent == null) {
+            return;
+        } 
+        addSubscribingLink(uiHintEvent.getUiHintContainer());
+        final AjaxRequestTarget target = uiHintEvent.getTarget();
+        if(target != null) {
+            target.add(subscribingLink);
+        }
+    }
+
+    // //////////////////////////////////////
+
+    protected PageClassRegistry getPageClassRegistry() {
+        final PageClassRegistryAccessor pcra = (PageClassRegistryAccessor) getApplication();
+        return pcra.getPageClassRegistry();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionModel.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionModel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionModel.java
new file mode 100644
index 0000000..936c3fd
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionModel.java
@@ -0,0 +1,146 @@
+/*
+ *  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.wicket.ui.errors;
+
+import java.util.Iterator;
+import java.util.List;
+import com.google.common.base.Throwables;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import org.apache.isis.applib.NonRecoverableException;
+import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
+import org.apache.isis.viewer.wicket.model.models.ModelAbstract;
+
+public class ExceptionModel extends ModelAbstract<List<StackTraceDetail>> {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final String MAIN_MESSAGE_IF_NOT_RECOGNIZED = "Sorry, an unexpected error occurred.";
+    
+    private List<StackTraceDetail> stackTraceDetailList;
+    private boolean recognized;
+    private boolean authorizationCause;
+
+    private final String mainMessage;
+
+    public static ExceptionModel create(String recognizedMessageIfAny, Exception ex) {
+        return new ExceptionModel(recognizedMessageIfAny, ex);
+    }
+
+    /**
+     * Three cases: authorization exception, else recognized, else or not recognized.
+     * @param recognizedMessageIfAny
+     * @param ex
+     */
+    private ExceptionModel(String recognizedMessageIfAny, Exception ex) {
+
+        final ObjectMember.AuthorizationException authorizationException = causalChainOf(ex, ObjectMember.AuthorizationException.class);
+        if(authorizationException != null) {
+            this.authorizationCause = true;
+            this.mainMessage = authorizationException.getMessage();
+        } else {
+            this.authorizationCause = false;
+            if(recognizedMessageIfAny != null) {
+                this.recognized = true;
+                this.mainMessage = recognizedMessageIfAny;
+            } else {
+                this.recognized =false;
+
+                // see if we can find a NonRecoverableException in the stack trace
+                Iterable<NonRecoverableException> appEx = Iterables.filter(Throwables.getCausalChain(ex), NonRecoverableException.class);
+                Iterator<NonRecoverableException> iterator = appEx.iterator();
+                NonRecoverableException nonRecoverableException = iterator.hasNext() ? iterator.next() : null;
+
+                this.mainMessage = nonRecoverableException != null? nonRecoverableException.getMessage() : MAIN_MESSAGE_IF_NOT_RECOGNIZED;
+            }
+        }
+        stackTraceDetailList = asStackTrace(ex);
+    }
+
+
+    @Override
+    protected List<StackTraceDetail> load() {
+        return stackTraceDetailList;
+    }
+
+    private static <T extends Exception> T causalChainOf(Exception ex, Class<T> exType) {
+
+        final List<Throwable> causalChain = Throwables.getCausalChain(ex);
+        for (Throwable cause : causalChain) {
+            if(exType.isAssignableFrom(cause.getClass())) {
+                return (T)cause;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void setObject(List<StackTraceDetail> stackTraceDetail) {
+        if(stackTraceDetail == null) {
+            return;
+        }
+        this.stackTraceDetailList = stackTraceDetail;
+    }
+
+    public boolean isRecognized() {
+        return recognized;
+    }
+
+    public String getMainMessage() {
+        return mainMessage;
+    }
+
+    /**
+     * Whether this was an authorization exception (so UI can suppress information, eg stack trace).
+     */
+    public boolean isAuthorizationException() {
+        return authorizationCause;
+    }
+
+
+    public List<StackTraceDetail> getStackTrace() {
+        return stackTraceDetailList;
+    }
+
+    private static List<StackTraceDetail> asStackTrace(Throwable ex) {
+        List<StackTraceDetail> stackTrace = Lists.newArrayList();
+        List<Throwable> causalChain = Throwables.getCausalChain(ex);
+        boolean firstTime = true;
+        for(Throwable cause: causalChain) {
+            if(!firstTime) {
+                stackTrace.add(StackTraceDetail.spacer());
+                stackTrace.add(StackTraceDetail.causedBy());
+                stackTrace.add(StackTraceDetail.spacer());
+            } else {
+                firstTime = false;
+            }
+            stackTrace.add(StackTraceDetail.exceptionClassName(cause));
+            stackTrace.add(StackTraceDetail.exceptionMessage(cause));
+            addStackTraceElements(cause, stackTrace);
+        }
+        return stackTrace;
+    }
+
+    private static void addStackTraceElements(Throwable ex, List<StackTraceDetail> stackTrace) {
+        for (StackTraceElement el : ex.getStackTrace()) {
+            stackTrace.add(StackTraceDetail.element(el));
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.css
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.css b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.css
new file mode 100644
index 0000000..521283c
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.css
@@ -0,0 +1,101 @@
+/*
+ *  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.
+ */
+
+ .exceptionStackTracePanel .errorInfo {
+    margin-left: 50px;
+    margin-right: 50px;
+    padding-top: 50px;
+}
+
+
+.exceptionStackTracePanel .mainMessage {
+    background:#FFFFFF;
+    border-radius:4px;
+    -moz-border-radius:4px;
+    -webkit-border-radius:4px;
+    padding: 15px;
+    display: block;
+    text-align:center;
+    font-size:1.2em;
+}
+
+ .exceptionStackTracePanel .errorDetail {
+    margin-top: 30px; 
+}
+
+.exceptionStackTracePanel .heading {
+    border-radius:4px;
+    -moz-border-radius:4px;
+    -webkit-border-radius:4px;
+    background-color:#F0EFEA;
+
+    display:block;
+    font-style:normal !important;
+
+    padding:1px 6px 1px 6px;
+}
+
+.exceptionStackTracePanel .heading span {
+    display:block;
+    font-style:normal !important;
+    padding:3px 3px 3px 3px;
+    font-size: 0.8em;
+    font-weight:bold;
+}
+
+.exceptionStackTracePanel .heading:hover {
+    background-color:#FFFFFF;
+    cursor: pointer;
+}
+
+.exceptionStackTracePanel h3 {
+    font-size: larger;
+}
+
+.exceptionStackTracePanel .exceptionMessage {
+    margin-top: 30px; 
+}
+
+.exceptionStackTracePanel .exceptionStackTrace .caused_by_label {
+    margin-top: 30px;
+    font-style:normal !important;
+    font-size: 0.8em;
+    font-weight:bold;
+    color: #46423C;
+}
+
+.exceptionStackTracePanel .exceptionStackTrace .exception_class_name {
+    margin-top: 15px;
+    font-size: 1.2em;
+    font-weight:bold;
+    color: #46423C;
+}
+
+.exceptionStackTracePanel .exceptionStackTrace .exception_message {
+    margin-top: 10px;
+    margin-bottom: 10px;
+    font-style: italic;
+    font-size: 1.0em;
+    color: #00477F;
+}
+
+.exceptionStackTracePanel .exceptionStackTrace .stacktrace_element{
+    margin-left: 30px;
+}
+ 
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.html
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.html b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.html
new file mode 100644
index 0000000..2d61e32
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.html
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html>
+<!--
+  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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd"  
+      xml:lang="en"  
+      lang="en">
+    <head>
+        <wicket:link>
+            <!--<link href="ExceptionStackTracePanel.css" rel="stylesheet" type="text/css"/>-->
+        </wicket:link>
+    </head>
+    <body>
+        <wicket:panel>
+            <div class="exceptionStackTracePanel">
+                <div class="errorInfo clear">
+                    <h4 wicket:id="mainMessage" class="mainMessage">[main message text]</h4>
+                    <div class="errorDetail panel panel-default" wicket:id="exceptionDetail">
+                        <div class="heading panel-heading"><span class="panel-title">Show detail</span></div>
+                        <div class="content panel-body">
+                            <div class="exceptionStackTrace">
+                                <h3>Stack trace:</h3>
+                                <ul>
+                                    <li wicket:id="stackTraceElement">
+                                        <span wicket:id="stackTraceElementLine">[stack trace element line]</span>
+                                    </li>
+                                </ul>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </wicket:panel>
+    </body>
+</html>
+
+

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.java
new file mode 100644
index 0000000..7504c47
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.java
@@ -0,0 +1,73 @@
+/*
+ *  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.wicket.ui.errors;
+
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptReferenceHeaderItem;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
+import org.apache.isis.viewer.wicket.ui.util.Components;
+
+public class ExceptionStackTracePanel extends Panel {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final String ID_MAIN_MESSAGE = "mainMessage";
+
+    private static final String ID_EXCEPTION_DETAIL = "exceptionDetail";
+
+    private static final String ID_STACK_TRACE_ELEMENT = "stackTraceElement";
+    private static final String ID_LINE = "stackTraceElementLine";
+
+    private static final JavaScriptResourceReference DIV_TOGGLE_JS = new JavaScriptResourceReference(ExceptionStackTracePanel.class, "div-toggle.js");
+
+    public ExceptionStackTracePanel(String id, ExceptionModel exceptionModel) {
+        super(id, exceptionModel);
+
+        final String mainMessage = exceptionModel.getMainMessage();
+        final Label label = new Label(ID_MAIN_MESSAGE, mainMessage);
+
+        // to avoid potential XSS attacks, no longer escape model strings
+        // (risk is low but could just happen: error message being rendered might accidentally or deliberately contain rogue Javascript)
+        // label.setEscapeModelStrings(false);
+
+        add(label);
+
+        final boolean suppressDetail = exceptionModel.isAuthorizationException() || exceptionModel.isRecognized();
+        if(suppressDetail) {
+            Components.permanentlyHide(this, ID_EXCEPTION_DETAIL);
+        } else {
+
+                MarkupContainer container = new WebMarkupContainer(ID_EXCEPTION_DETAIL) {
+                private static final long serialVersionUID = 1L;
+                @Override
+                public void renderHead(IHeaderResponse response) {
+                    response.render(JavaScriptReferenceHeaderItem.forReference(DIV_TOGGLE_JS));
+                }
+            };
+            container.add(new StackTraceListView(ID_STACK_TRACE_ELEMENT, ExceptionStackTracePanel.ID_LINE, exceptionModel.getStackTrace()));
+            add(container);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/JGrowlBehaviour.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/JGrowlBehaviour.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/JGrowlBehaviour.java
new file mode 100644
index 0000000..90084e6
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/JGrowlBehaviour.java
@@ -0,0 +1,81 @@
+/*
+ *  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.wicket.ui.errors;
+
+import com.google.common.base.Strings;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptHeaderItem;
+import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
+
+import org.apache.isis.applib.RecoverableException;
+import org.apache.isis.core.commons.authentication.MessageBroker;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+
+
+/**
+ * Attach to any Ajax button that might trigger a notification (ie calls
+ * {@link MessageBroker#addMessage(String)}, {@link MessageBroker#addWarning(String)},
+ * {@link MessageBroker#setApplicationError(String)} or throws an {@link RecoverableException}). 
+ * 
+ * <p>
+ * Attach using the standard Wicket code:
+ * <pre>
+ * Button editButton = new AjaxButton(ID_EDIT_BUTTON, Model.of("Edit")) { ... }
+ * editButton.add(new JGrowlBehaviour());
+ * </pre>
+ */
+public class JGrowlBehaviour extends AbstractDefaultAjaxBehavior {
+
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    protected void respond(AjaxRequestTarget target) {
+        String feedbackMsg = JGrowlUtil.asJGrowlCalls(getMessageBroker());
+        if(!Strings.isNullOrEmpty(feedbackMsg)) {
+            target.appendJavaScript(feedbackMsg);
+        }
+    }
+
+    @Override
+    public void renderHead(Component component, IHeaderResponse response) {
+        super.renderHead(component, response);
+
+        renderFeedbackMessages(response);
+    }
+
+    public void renderFeedbackMessages(IHeaderResponse response) {
+        response.render(JavaScriptHeaderItem.forReference(new JavaScriptResourceReference(JGrowlBehaviour.class, "js/bootstrap-growl.js")));
+
+        String feedbackMsg = JGrowlUtil.asJGrowlCalls(getMessageBroker());
+        if(!Strings.isNullOrEmpty(feedbackMsg)) {
+            response.render(OnDomReadyHeaderItem.forScript(feedbackMsg));
+        }
+    }
+
+
+    protected MessageBroker getMessageBroker() {
+        return IsisContext.getMessageBroker();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/JGrowlUtil.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/JGrowlUtil.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/JGrowlUtil.java
new file mode 100644
index 0000000..686e011
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/JGrowlUtil.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.wicket.ui.errors;
+
+import org.apache.wicket.util.string.Strings;
+import org.apache.isis.core.commons.authentication.MessageBroker;
+
+public class JGrowlUtil {
+    
+    private JGrowlUtil(){}
+
+    public static String asJGrowlCalls(final MessageBroker messageBroker) {
+        final StringBuilder buf = new StringBuilder();
+        
+        for (String info : messageBroker.getMessages()) {
+            addJGrowlCall(info, "info", false, buf);
+        }
+        for (String warning : messageBroker.getWarnings()) {
+            addJGrowlCall(warning, "warning", true, buf);
+        }
+        
+        final String error =  messageBroker.getApplicationError();
+        if(error!=null) {
+            addJGrowlCall(error, "danger", true, buf);
+        }
+        return buf.toString();
+    }
+
+    private static void addJGrowlCall(final String origMsg, final String cssClassSuffix, boolean sticky, final StringBuilder buf) {
+        final CharSequence escapedMsg = escape(origMsg);
+        buf.append("$.growl(\"")
+            .append(escapedMsg)
+            .append("&#160;&#160;&#160;") // add some space so that the dismiss icon (x) doesn't overlap with the text
+            .append('"');
+        buf.append(", {");
+        buf.append("type: \"").append(cssClassSuffix).append('"');
+        buf.append(", delay: " + (sticky ? "0" : "2000"));
+        buf.append(", placement: { from: 'top', align: 'right' }");
+        buf.append(", offset: 50");
+        buf.append('}');
+        buf.append(");\n");
+    }
+
+    static String escape(String origMsg) {
+        final String escaped = Strings.escapeMarkup(origMsg).toString();
+
+        // convert (what would originally have been either) ' or " to '
+        return escaped
+                .replace("&quot;", "'")
+                .replace("&#039;", "'");
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/StackTraceDetail.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/StackTraceDetail.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/StackTraceDetail.java
new file mode 100644
index 0000000..9f73c4a
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/StackTraceDetail.java
@@ -0,0 +1,78 @@
+/*
+ *  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.wicket.ui.errors;
+
+import java.io.Serializable;
+
+public class StackTraceDetail implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+
+    public static StackTraceDetail exceptionClassName(Throwable cause) {
+        return new StackTraceDetail(StackTraceDetail.Type.EXCEPTION_CLASS_NAME, cause.getClass().getName());
+    }
+
+    public static StackTraceDetail exceptionMessage(Throwable cause) {
+        return new StackTraceDetail(StackTraceDetail.Type.EXCEPTION_MESSAGE, cause.getMessage());
+    }
+
+    public static StackTraceDetail element(StackTraceElement el) {
+        StringBuilder buf = new StringBuilder();
+        buf .append("    ")
+            .append(el.getClassName())
+            .append("#")
+            .append(el.getMethodName())
+            .append("(")
+            .append(el.getFileName())
+            .append(":")
+            .append(el.getLineNumber())
+            .append(")\n")
+            ;
+        return new StackTraceDetail(StackTraceDetail.Type.STACKTRACE_ELEMENT, buf.toString());
+    }
+
+    public static StackTraceDetail spacer() {
+        return new StackTraceDetail(Type.LITERAL, "");
+    }
+
+    public static StackTraceDetail causedBy() {
+        return new StackTraceDetail(Type.LITERAL, "Caused by:");
+    }
+
+    enum Type {
+        EXCEPTION_CLASS_NAME,
+        EXCEPTION_MESSAGE,
+        STACKTRACE_ELEMENT,
+        LITERAL
+    }
+    private final Type type;
+    private final String line;
+    
+    public StackTraceDetail(Type type, String line) {
+        this.type = type;
+        this.line = line;
+    }
+    public StackTraceDetail.Type getType() {
+        return type;
+    }
+    public String getLine() {
+        return line;
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/StackTraceListView.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/StackTraceListView.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/StackTraceListView.java
new file mode 100644
index 0000000..b703a28
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/StackTraceListView.java
@@ -0,0 +1,45 @@
+/*
+ *  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.wicket.ui.errors;
+
+import java.util.List;
+
+import org.apache.wicket.behavior.AttributeAppender;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+
+public final class StackTraceListView extends ListView<StackTraceDetail> {
+    
+    private static final long serialVersionUID = 1L;
+    private final String idLine;
+
+    public StackTraceListView(String id, String idLine, List<? extends org.apache.isis.viewer.wicket.ui.errors.StackTraceDetail> list) {
+        super(id, list);
+        this.idLine = idLine;
+    }
+
+    @Override
+    protected void populateItem(ListItem<StackTraceDetail> item) {
+        final StackTraceDetail detail = item.getModelObject();
+        Label label = new Label(idLine, detail.getLine());
+        item.add(new AttributeAppender("class", detail.getType().name().toLowerCase()));
+        item.add(label);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/div-toggle.js
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/div-toggle.js b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/div-toggle.js
new file mode 100644
index 0000000..f5cc913
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/div-toggle.js
@@ -0,0 +1,26 @@
+/*
+ *  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.
+ */
+
+jQuery(document).ready(function() {
+  jQuery(".exceptionStackTracePanel .content").hide();
+  jQuery(".exceptionStackTracePanel .heading").click(function()
+  {
+    jQuery(this).next(".exceptionStackTracePanel .content").slideToggle(500);
+  });
+});

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/js/bootstrap-growl.js
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/js/bootstrap-growl.js b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/js/bootstrap-growl.js
new file mode 100644
index 0000000..ec3012e
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/js/bootstrap-growl.js
@@ -0,0 +1,312 @@
+/*
+*  Project: Bootstrap Growl - v2.0.0
+*  Description: Turns standard Bootstrap alerts into "Growl-like" notifications.
+*  Author: Mouse0270 aka Robert McIntosh
+*  License: MIT License
+*  Website: https://github.com/mouse0270/bootstrap-growl
+*/
+;(function ( $, window, document, undefined ) {
+	// Create the defaults once
+	var pluginName = "growl",
+		dataKey = "plugin_" + pluginName,
+		defaults = {
+			element: 'body',
+			type: "info",
+			allow_dismiss: true,
+			placement: {
+				from: "top",
+				align: "right"
+			},
+			offset: 20,
+			spacing: 10,
+			z_index: 1031,
+			delay: 5000,
+			timer: 1000,
+			url_target: '_blank',
+			mouse_over: false,
+			animate: {
+				enter: 'animated fadeInDown',
+				exit: 'animated fadeOutUp'
+			},
+			onShow: null,
+			onShown: null,
+			onHide: null,
+			onHidden: null,
+			icon_type: 'class',
+			template: '<div data-growl="container" class="alert" role="alert"><button type="button" class="close" data-growl="dismiss"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button><span data-growl="icon"></span><span data-growl="title"></span><span data-growl="message"></span><a href="#" data-growl="url"></a></div>'
+		};	
+
+	// The actual plugin constructor
+	var setDefaults = function(element, options) {
+		defaults = $.extend(true, {}, defaults, options);
+	},
+	closeAll = function(options) {
+		if (!options) {
+			$('[data-growl="container"]').find('[data-growl="dismiss"]').trigger('click');
+		}else{
+			$('[data-growl="container"][data-growl-position="'+options+'"]').find('[data-growl="dismiss"]').trigger('click');			
+		}
+	},
+	Plugin = function (element, content, options) {
+		var content = {
+			content: {
+				message: typeof content == 'object' ? content.message : content,
+				title: content.title ? content.title : null,
+				icon: content.icon ? content.icon : null,
+				url: content.url ? content.url : null
+			}
+		};
+
+		options = $.extend(true, {}, content, options);
+		this.settings = $.extend(true, {}, defaults, options);
+		plugin = this;
+		init(options, this.settings, plugin);	
+		this.$template = $template;
+	},
+	init = function (options, settings, plugin) {
+
+		var base = {
+				settings: settings,
+				$element: $(settings.element),
+				template: settings.template
+			};
+
+		if (typeof settings.offset == 'number') {
+		    settings.offset = {
+		    	x: settings.offset,
+		    	y: settings.offset
+		    };
+		}
+
+		$template = buildGrowl(base);
+		addContent($template, base.settings);
+		placement($template, base.settings);
+		bindControls($template, base.settings,plugin);
+	},
+	buildGrowl = function(base) {
+
+		var $template = $(base.settings.template);
+
+		$template.addClass('alert-' + base.settings.type);
+		$template.attr('data-growl-position', base.settings.placement.from + '-' + base.settings.placement.align);
+
+		$template.find('[data-growl="dismiss"]').css('display', 'none');
+		if (base.settings.allow_dismiss) {
+			$template.find('[data-growl="dismiss"]').css('display', 'inline-block');
+		}
+
+		return $template;
+	},
+	addContent = function($template, settings) {
+
+		$template.find('[data-growl="dismiss"]').css({
+			'position': 'absolute',
+			'top': '5px',
+			'right': '10px',
+			'z-index': ((settings.z_index-1) >= 1 ? (settings.z_index-1) : 1)
+		});
+
+		if (settings.content.icon) {
+			if (settings.icon_type.toLowerCase() == 'class') {
+				$template.find('[data-growl="icon"]').addClass(settings.content.icon);
+			}else{
+				if ($template.find('[data-growl="icon"]').is('img')) {
+					$template.find('[data-growl="icon"]').attr('src', settings.content.icon);
+				}else{
+					$template.find('[data-growl="icon"]').append('<img src="'+settings.content.icon+'" />');
+				}
+			}
+		}
+
+		if (settings.content.title) {
+			$template.find('[data-growl="title"]').html(settings.content.title);
+		}
+
+		if (settings.content.message) {
+			$template.find('[data-growl="message"]').html(settings.content.message);
+		}
+
+		if (settings.content.url) {
+			$template.find('[data-growl="url"]').attr('href', settings.content.url).attr('target', settings.url_target);
+			$template.find('[data-growl="url"]').css({
+				'position': 'absolute',
+				'top': '0px',
+				'left': '0px',
+				'width': '100%',
+				'height': '100%',
+				'z-index': ((settings.z_index-2) >= 1 ? (settings.z_index-2) : 1)
+			});
+		}
+	},
+	placement = function($template, settings) {
+		var offsetAmt = settings.offset.y,
+			gCSS = {
+				'position': (settings.element === 'body' ? 'fixed' : 'absolute'),
+				'margin': 0,
+				'z-index': settings.z_index,
+				'display': 'inline-block'
+			},
+			hasAnimation = false;
+
+		$('[data-growl-position="' + settings.placement.from + '-' + settings.placement.align + '"]').each(function() {
+			return offsetAmt = Math.max(offsetAmt, parseInt($(this).css(settings.placement.from)) + $(this).outerHeight() + settings.spacing);
+		});
+
+		gCSS[settings.placement.from] = offsetAmt + "px";
+		$template.css(gCSS);
+
+		if (settings.onShow) {
+			settings.onShow(event);
+		}
+
+		$(settings.element).append($template);
+
+		switch (settings.placement.align) {
+			case 'center':
+				$template.css({
+					'left': '50%',
+					'marginLeft': -($template.outerWidth() / 2) + 'px'
+				});
+				break;
+			case 'left':
+				$template.css('left', settings.offset.x + 'px');
+				break;
+			case 'right':
+				$template.css('right', settings.offset.x + 'px');
+				break;
+		}
+		$template.addClass('growl-animated');
+
+		$template.one('webkitAnimationStart oanimationstart MSAnimationStart animationstart', function(event) {
+			hasAnimation = true;
+		});
+			
+		$template.one('webkitAnimationEnd oanimationend MSAnimationEnd animationend', function(event) {
+			if (settings.onShown) {
+				settings.onShown(event);
+			}
+		});
+
+		setTimeout(function() {
+			if (!hasAnimation) {
+				if (settings.onShown) {
+					settings.onShown(event);
+				}
+			}
+		}, 600);
+	},
+	bindControls = function($template, settings, plugin) {
+		$template.addClass(settings.animate.enter);
+
+		$template.find('[data-growl="dismiss"]').on('click', function() {
+			plugin.close();
+		});
+
+		$template.on('mouseover', function(e) {
+			$template.addClass('hovering');
+		}).on('mouseout', function() {
+			$template.removeClass('hovering');
+		});
+
+		if (settings.delay >= 1) {
+			$template.data('growl-delay', settings.delay);
+			var timer = setInterval(function() {
+
+				var delay = parseInt($template.data('growl-delay')) - settings.timer;
+				if ((!$template.hasClass('hovering') && settings.mouse_over == 'pause') || settings.mouse_over != 'pause') {
+					$template.data('growl-delay', delay);
+				}
+
+				if (delay <= 0) {
+					clearInterval(timer);
+					plugin.close();
+				}
+			}, settings.timer);
+		}
+	};
+
+	// Avoid Plugin.prototype conflicts
+	Plugin.prototype = {
+		update: function(command, update) {
+			switch (command) {
+				case 'icon':
+					if (this.settings.icon_type.toLowerCase() == 'class') {
+						this.$template.find('[data-growl="icon"]').removeClass(this.settings.content.icon);
+						this.$template.find('[data-growl="icon"]').addClass(update);
+					}else{
+						if (this.$template.find('[data-growl="icon"]').is('img')) {
+							this.$template.find('[data-growl="icon"]')
+						}else{
+							this.$template.find('[data-growl="icon"]').find('img').attr().attr('src', update);
+						}
+					}
+					break;
+				case 'url':
+					this.$template.find('[data-growl="url"]').attr('href', update);
+					break;
+				case 'type':
+					this.$template.removeClass('alert-' + this.settings.type);
+					this.$template.addClass('alert-' + update);
+					break;
+				default:
+					this.$template.find('[data-growl="' + command +'"]').html(update);
+			}
+
+			return this;
+		},
+		close: function() {
+			var base = this.$template,
+				settings = this.settings,
+				posX = base.css(settings.placement.from),
+				hasAnimation = false;
+
+			if (settings.onHide) {
+				settings.onHide(event);
+			}
+
+			base.addClass(this.settings.animate.exit);
+
+			base.nextAll('[data-growl-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]').each(function() {
+				$(this).css(settings.placement.from, posX);
+				posX = (parseInt(posX)+(settings.spacing)) + $(this).outerHeight();
+			});
+
+			base.one('webkitAnimationStart oanimationstart MSAnimationStart animationstart', function(event) {
+				hasAnimation = true;
+			});
+			
+			base.one('webkitAnimationEnd oanimationend MSAnimationEnd animationend', function(event) {
+				$(this).remove();
+				if (settings.onHidden) {
+					settings.onHidden(event);
+				}
+			});
+
+			setTimeout(function() {
+				if (!hasAnimation) {
+					base.remove();
+					if (settings.onHidden) {
+						settings.onHidden(event);
+					}
+				}
+			}, 100);
+
+ 			return this;
+		}
+	};
+
+	// A really lightweight plugin wrapper around the constructor,
+	// preventing against multiple instantiations
+	$.growl = function ( content, options ) {
+		if (content == false && options.command == "closeAll") {
+			closeAll(options.position);
+			return false;
+		}else if (content == false) {
+			setDefaults(this, options);
+			return false;
+		}
+		var plugin = new Plugin( this, content, options );
+		return plugin;
+	};
+
+})( jQuery, window, document );
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/js/bootstrap-growl.min.js
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/js/bootstrap-growl.min.js b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/js/bootstrap-growl.min.js
new file mode 100644
index 0000000..ad059cc
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/js/bootstrap-growl.min.js
@@ -0,0 +1,2 @@
+/* Project: Bootstrap Growl - v2.0.0 | Author: Mouse0270 aka Robert McIntosh | License: MIT License | Website: https://github.com/mouse0270/bootstrap-growl */
+(function(e,t,n,r){var i="growl",s="plugin_"+i,o={element:"body",type:"info",allow_dismiss:true,placement:{from:"top",align:"right"},offset:20,spacing:10,z_index:1031,delay:5e3,timer:1e3,url_target:"_blank",mouse_over:false,animate:{enter:"animated fadeInDown",exit:"animated fadeOutUp"},onShow:null,onShown:null,onHide:null,onHidden:null,icon_type:"class",template:'<div data-growl="container" class="alert" role="alert"><button type="button" class="close" data-growl="dismiss"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button><span data-growl="icon"></span><span data-growl="title"></span><span data-growl="message"></span><a href="#" data-growl="url"></a></div>'};var u=function(t,n){o=e.extend(true,{},o,n)},a=function(t){if(!t){e('[data-growl="container"]').find('[data-growl="dismiss"]').trigger("click")}else{e('[data-growl="container"][data-growl-position="'+t+'"]').find('[data-growl="dismiss"]').trigger("click")}},f=function(t,n,r){var n={content:{messa
 ge:typeof n=="object"?n.message:n,title:n.title?n.title:null,icon:n.icon?n.icon:null,url:n.url?n.url:null}};r=e.extend(true,{},n,r);this.settings=e.extend(true,{},o,r);plugin=this;l(r,this.settings,plugin);this.$template=$template},l=function(t,n,r){var i={settings:n,$element:e(n.element),template:n.template};if(typeof n.offset=="number"){n.offset={x:n.offset,y:n.offset}}$template=c(i);h($template,i.settings);p($template,i.settings);d($template,i.settings,r)},c=function(t){var n=e(t.settings.template);n.addClass("alert-"+t.settings.type);n.attr("data-growl-position",t.settings.placement.from+"-"+t.settings.placement.align);n.find('[data-growl="dismiss"]').css("display","none");if(t.settings.allow_dismiss){n.find('[data-growl="dismiss"]').css("display","inline-block")}return n},h=function(e,t){e.find('[data-growl="dismiss"]').css({position:"absolute",top:"5px",right:"10px","z-index":t.z_index-1>=1?t.z_index-1:1});if(t.content.icon){if(t.icon_type.toLowerCase()=="class"){e.find('[data
 -growl="icon"]').addClass(t.content.icon)}else{if(e.find('[data-growl="icon"]').is("img")){e.find('[data-growl="icon"]').attr("src",t.content.icon)}else{e.find('[data-growl="icon"]').append('<img src="'+t.content.icon+'" />')}}}if(t.content.title){e.find('[data-growl="title"]').html(t.content.title)}if(t.content.message){e.find('[data-growl="message"]').html(t.content.message)}if(t.content.url){e.find('[data-growl="url"]').attr("href",t.content.url).attr("target",t.url_target);e.find('[data-growl="url"]').css({position:"absolute",top:"0px",left:"0px",width:"100%",height:"100%","z-index":t.z_index-2>=1?t.z_index-2:1})}},p=function(t,n){var r=n.offset.y,i={position:n.element==="body"?"fixed":"absolute",margin:0,"z-index":n.z_index,display:"inline-block"},s=false;e('[data-growl-position="'+n.placement.from+"-"+n.placement.align+'"]').each(function(){return r=Math.max(r,parseInt(e(this).css(n.placement.from))+e(this).outerHeight()+n.spacing)});i[n.placement.from]=r+"px";t.css(i);if(n.on
 Show){n.onShow(event)}e(n.element).append(t);switch(n.placement.align){case"center":t.css({left:"50%",marginLeft:-(t.outerWidth()/2)+"px"});break;case"left":t.css("left",n.offset.x+"px");break;case"right":t.css("right",n.offset.x+"px");break}t.addClass("growl-animated");t.one("webkitAnimationStart oanimationstart MSAnimationStart animationstart",function(e){s=true});t.one("webkitAnimationEnd oanimationend MSAnimationEnd animationend",function(e){if(n.onShown){n.onShown(e)}});setTimeout(function(){if(!s){if(n.onShown){n.onShown(event)}}},600)},d=function(e,t,n){e.addClass(t.animate.enter);e.find('[data-growl="dismiss"]').on("click",function(){n.close()});e.on("mouseover",function(t){e.addClass("hovering")}).on("mouseout",function(){e.removeClass("hovering")});if(t.delay>=1){e.data("growl-delay",t.delay);var r=setInterval(function(){var i=parseInt(e.data("growl-delay"))-t.timer;if(!e.hasClass("hovering")&&t.mouse_over=="pause"||t.mouse_over!="pause"){e.data("growl-delay",i)}if(i<=0){c
 learInterval(r);n.close()}},t.timer)}};f.prototype={update:function(e,t){switch(e){case"icon":if(this.settings.icon_type.toLowerCase()=="class"){this.$template.find('[data-growl="icon"]').removeClass(this.settings.content.icon);this.$template.find('[data-growl="icon"]').addClass(t)}else{if(this.$template.find('[data-growl="icon"]').is("img")){this.$template.find('[data-growl="icon"]')}else{this.$template.find('[data-growl="icon"]').find("img").attr().attr("src",t)}}break;case"url":this.$template.find('[data-growl="url"]').attr("href",t);break;case"type":this.$template.removeClass("alert-"+this.settings.type);this.$template.addClass("alert-"+t);break;default:this.$template.find('[data-growl="'+e+'"]').html(t)}return this},close:function(){var t=this.$template,n=this.settings,r=t.css(n.placement.from),i=false;if(n.onHide){n.onHide(event)}t.addClass(this.settings.animate.exit);t.nextAll('[data-growl-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]').each(f
 unction(){e(this).css(n.placement.from,r);r=parseInt(r)+n.spacing+e(this).outerHeight()});t.one("webkitAnimationStart oanimationstart MSAnimationStart animationstart",function(e){i=true});t.one("webkitAnimationEnd oanimationend MSAnimationEnd animationend",function(t){e(this).remove();if(n.onHidden){n.onHidden(t)}});setTimeout(function(){if(!i){t.remove();if(n.onHidden){n.onHidden(event)}}},100);return this}};e.growl=function(e,t){if(e==false&&t.command=="closeAll"){a(t.position);return false}else if(e==false){u(this,t);return false}var n=new f(this,e,t);return n}})(jQuery,window,document)
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/BookmarkedPagesModelProvider.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/BookmarkedPagesModelProvider.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/BookmarkedPagesModelProvider.java
new file mode 100644
index 0000000..f4051ea
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/BookmarkedPagesModelProvider.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.wicket.ui.pages;
+
+import org.apache.isis.viewer.wicket.model.models.BookmarkedPagesModel;
+
+
+
+public interface BookmarkedPagesModelProvider  {
+    
+    BookmarkedPagesModel getBookmarkedPagesModel();
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/BootstrapOverridesCssResourceReference.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/BootstrapOverridesCssResourceReference.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/BootstrapOverridesCssResourceReference.java
new file mode 100644
index 0000000..f5c9157
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/BootstrapOverridesCssResourceReference.java
@@ -0,0 +1,33 @@
+/*
+ *  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.wicket.ui.pages;
+
+import org.apache.wicket.request.resource.CssResourceReference;
+
+/**
+ * A CSS resource reference that provides CSS rules which override the CSS rules
+ * provided by the currently active Bootstrap theme.
+ * Usually the overrides rules are about sizes and weights, but should not change any colors
+ */
+public class BootstrapOverridesCssResourceReference extends CssResourceReference {
+
+    public BootstrapOverridesCssResourceReference() {
+        super(BootstrapOverridesCssResourceReference.class, "bootstrap-overrides.css");
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/EmailVerificationUrlService.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/EmailVerificationUrlService.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/EmailVerificationUrlService.java
new file mode 100644
index 0000000..71c3b2c
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/EmailVerificationUrlService.java
@@ -0,0 +1,52 @@
+/*
+ *  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.wicket.ui.pages;
+
+import java.io.Serializable;
+import org.apache.wicket.Page;
+import org.apache.isis.viewer.wicket.model.models.PageType;
+
+/**
+ * A Wicket specific service that may be used to create a link to a
+ * page by {@link org.apache.isis.viewer.wicket.model.models.PageType page type}
+ * with encoded/encrypted datum as first indexed parameter in the url for
+ * mail verification purposes.
+ */
+public interface EmailVerificationUrlService extends Serializable {
+
+    /**
+     * Creates a url to the passed <em>pageType</em> by encrypting the given
+     * <em>datum</em> as a first indexed parameter
+     *
+     * @param pageType The type of the page to link to
+     * @param datum The data to encrypt in the url
+     * @return The full url to the page with the encrypted data
+     */
+    String createVerificationUrl(PageType pageType, String datum);
+
+    /**
+     * Creates a url to the passed <em>pageClass</em> by encrypting the given
+     * <em>datum</em> as a first indexed parameter
+     *
+     * @param pageClass The class of the page to link to
+     * @param datum The data to encrypt in the url
+     * @return The full url to the page with the encrypted data
+     */
+    String createVerificationUrl(final Class<? extends Page> pageClass, final String datum);
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.html
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.html b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.html
new file mode 100644
index 0000000..cf8a91f
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.html
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html>
+<!--
+  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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org"
+      xml:lang="en"  
+      lang="en">
+    <head>
+        <meta charset="utf-8">
+        <meta http-equiv="X-UA-COMPATIBLE" content="IE=edge">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no">
+        <title wicket:id="pageTitle"></title>
+        <link rel="icon" wicket:id="favicon">
+        <wicket:header-items/>
+    </head>
+    <body>
+        <div id="container" class="page container-fluid">
+            <div wicket:id="theme">
+
+                <wicket:container wicket:id="header"></wicket:container>
+
+                <div class="clear"/>
+
+                <div id="body">
+
+                    <wicket:child/>
+
+                    <div class="clearfix" style="margin-bottom: 100px"></div>
+                </div>
+
+                <div wicket:id="actionPromptModalWindow"></div>
+
+                <wicket:container wicket:id="footer"></wicket:container>
+
+            </div>
+
+        </div>
+        <div class="javascriptInFooter">
+            <wicket:container wicket:id="footerJS"/>
+        </div>
+        <div id="veil">
+            <div class="text-center">
+                <i class="fa fa-spinner fa-spin fa-5x"></i>
+            </div>
+        </div>
+    </body>
+</html>

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.java
new file mode 100644
index 0000000..daf886e
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.java
@@ -0,0 +1,450 @@
+/*
+ *  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.wicket.ui.pages;
+
+import de.agilecoders.wicket.core.Bootstrap;
+import de.agilecoders.wicket.core.markup.html.references.BootlintJavaScriptReference;
+import de.agilecoders.wicket.core.settings.IBootstrapSettings;
+import de.agilecoders.wicket.core.settings.ITheme;
+import de.agilecoders.wicket.extensions.markup.html.bootstrap.icon.FontAwesomeCssReference;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import org.apache.wicket.Component;
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.Page;
+import org.apache.wicket.RestartResponseAtInterceptPageException;
+import org.apache.wicket.behavior.Behavior;
+import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.markup.head.CssHeaderItem;
+import org.apache.wicket.markup.head.CssReferenceHeaderItem;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptHeaderItem;
+import org.apache.wicket.markup.head.JavaScriptReferenceHeaderItem;
+import org.apache.wicket.markup.head.PriorityHeaderItem;
+import org.apache.wicket.markup.head.filter.HeaderResponseContainer;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.protocol.http.ClientProperties;
+import org.apache.wicket.protocol.http.WebSession;
+import org.apache.wicket.protocol.http.request.WebClientInfo;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.request.resource.CssResourceReference;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
+import org.apache.wicket.request.resource.PackageResource;
+import org.apache.wicket.request.resource.ResourceReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.isis.applib.services.exceprecog.ExceptionRecognizer;
+import org.apache.isis.applib.services.exceprecog.ExceptionRecognizerComposite;
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.commons.authentication.MessageBroker;
+import org.apache.isis.core.commons.config.IsisConfiguration;
+import org.apache.isis.core.metamodel.services.ServicesInjectorSpi;
+import org.apache.isis.core.metamodel.spec.SpecificationLoaderSpi;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
+import org.apache.isis.viewer.wicket.model.common.PageParametersUtils;
+import org.apache.isis.viewer.wicket.model.hints.IsisEnvelopeEvent;
+import org.apache.isis.viewer.wicket.model.hints.IsisEventLetterAbstract;
+import org.apache.isis.viewer.wicket.model.models.ActionPrompt;
+import org.apache.isis.viewer.wicket.model.models.ActionPromptProvider;
+import org.apache.isis.viewer.wicket.model.models.BookmarkableModel;
+import org.apache.isis.viewer.wicket.model.models.BookmarkedPagesModel;
+import org.apache.isis.viewer.wicket.model.models.EntityModel;
+import org.apache.isis.viewer.wicket.model.models.PageType;
+import org.apache.isis.viewer.wicket.ui.ComponentFactory;
+import org.apache.isis.viewer.wicket.ui.ComponentType;
+import org.apache.isis.viewer.wicket.ui.app.registry.ComponentFactoryRegistry;
+import org.apache.isis.viewer.wicket.ui.app.registry.ComponentFactoryRegistryAccessor;
+import org.apache.isis.viewer.wicket.ui.components.actionprompt.ActionPromptModalWindow;
+import org.apache.isis.viewer.wicket.ui.components.widgets.favicon.Favicon;
+import org.apache.isis.viewer.wicket.ui.errors.ExceptionModel;
+import org.apache.isis.viewer.wicket.ui.errors.JGrowlBehaviour;
+import org.apache.isis.viewer.wicket.ui.util.CssClassAppender;
+
+/**
+ * Convenience adapter for {@link WebPage}s built up using {@link ComponentType}s.
+ */
+public abstract class PageAbstract extends WebPage implements ActionPromptProvider {
+
+    private static Logger LOG = LoggerFactory.getLogger(PageAbstract.class);
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * @see <a href="http://github.com/brandonaaron/livequery">livequery</a>
+     */
+    private static final JavaScriptResourceReference JQUERY_LIVEQUERY_JS = new JavaScriptResourceReference(PageAbstract.class, "jquery.livequery.js");
+    private static final JavaScriptResourceReference JQUERY_ISIS_WICKET_VIEWER_JS = new JavaScriptResourceReference(PageAbstract.class, "jquery.isis.wicket.viewer.js");
+    
+    // not to be confused with the bootstrap theme...
+    // is simply a CSS class derived from the application's name
+    private static final String ID_THEME = "theme";
+
+    private static final String ID_BOOKMARKED_PAGES = "bookmarks";
+
+    private static final String ID_ACTION_PROMPT_MODAL_WINDOW = "actionPromptModalWindow";
+    
+    private static final String ID_PAGE_TITLE = "pageTitle";
+
+    private static final String ID_FAVICON = "favicon";
+
+    public static final String ID_MENU_LINK = "menuLink";
+
+    /**
+     * This is a bit hacky, but best way I've found to pass an exception over to the WicketSignInPage
+     * if there is a problem rendering this page.
+     */
+    public static ThreadLocal<ExceptionModel> EXCEPTION = new ThreadLocal<>();
+
+    private final List<ComponentType> childComponentIds;
+
+    /**
+     * {@link Inject}ed when {@link #init() initialized}.
+     */
+    @Inject
+    @Named("applicationName")
+    private String applicationName;
+
+    /**
+     * {@link Inject}ed when {@link #init() initialized}.
+     */
+    @Inject(optional = true)
+    @Named("applicationCss")
+    private String applicationCss;
+    
+    /**
+     * {@link Inject}ed when {@link #init() initialized}.
+     *///
+    @Inject(optional = true)
+    @Named("applicationJs")
+    private String applicationJs;
+
+    /**
+     * {@link Inject}ed when {@link #init() initialized}.
+     */
+    @Inject
+    private PageClassRegistry pageClassRegistry;
+
+    /**
+     * Top-level &lt;div&gt; to which all content is added.
+     *
+     * <p>
+     *     Has <code>protected</code> visibility so that subclasses can also add directly to this div.
+     * </p>
+     */
+    protected MarkupContainer themeDiv;
+
+    public PageAbstract(
+            final PageParameters pageParameters,
+            final String title,
+            final ComponentType... childComponentIds) {
+        super(pageParameters);
+
+        try {
+            // for breadcrumbs support
+            getSession().bind();
+            
+            setTitle(title);
+
+            add(new Favicon(ID_FAVICON));
+
+            themeDiv = new WebMarkupContainer(ID_THEME);
+            add(themeDiv);
+            if(applicationName != null) {
+                themeDiv.add(new CssClassAppender(CssClassAppender.asCssStyle(applicationName)));
+            }
+
+            MarkupContainer header = createPageHeader("header");
+            themeDiv.add(header);
+
+            MarkupContainer footer = createPageFooter("footer");
+            themeDiv.add(footer);
+
+            addActionPromptModalWindow(themeDiv);
+
+            this.childComponentIds = Collections.unmodifiableList(Arrays.asList(childComponentIds));
+
+            // ensure that all collected JavaScript contributions are loaded at the page footer
+            add(new HeaderResponseContainer("footerJS", "footerJS"));
+
+        } catch(final RuntimeException ex) {
+
+            LOG.error("Failed to construct page, going back to sign in page", ex);
+            
+            // REVIEW: similar code in WebRequestCycleForIsis
+            final  List<ExceptionRecognizer> exceptionRecognizers = getServicesInjector().lookupServices(ExceptionRecognizer.class);
+            final String recognizedMessageIfAny = new ExceptionRecognizerComposite(exceptionRecognizers).recognize(ex);
+            final ExceptionModel exceptionModel = ExceptionModel.create(recognizedMessageIfAny, ex);
+
+            getSession().invalidate();
+            getSession().clear();
+            
+            // for the WicketSignInPage to render
+            EXCEPTION.set(exceptionModel);
+
+            throw new RestartResponseAtInterceptPageException(getSignInPage());
+        }
+    }
+
+    /**
+     * Creates the component that should be used as a page header/navigation bar
+     *
+     * @param id The component id
+     * @return The container that should be used as a page header/navigation bar
+     */
+    protected MarkupContainer createPageHeader(final String id) {
+        Component header = getComponentFactoryRegistry().createComponent(ComponentType.HEADER, id, null);
+        return (MarkupContainer) header;
+    }
+
+    /**
+     * Creates the component that should be used as a page header/navigation bar
+     *
+     * @param id The component id
+     * @return The container that should be used as a page header/navigation bar
+     */
+    protected MarkupContainer createPageFooter(final String id) {
+        Component footer = getComponentFactoryRegistry().createComponent(ComponentType.FOOTER, id, null);
+        return (MarkupContainer) footer;
+    }
+
+
+    protected void setTitle(final String title) {
+        addOrReplace(new Label(ID_PAGE_TITLE, title != null? title: applicationName));
+    }
+
+    private Class<? extends Page> getSignInPage() {
+        return pageClassRegistry.getPageClass(PageType.SIGN_IN);
+    }
+
+    @Override
+    public void renderHead(final IHeaderResponse response) {
+        
+        super.renderHead(response);
+
+        response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forReference(getApplication().getJavaScriptLibrarySettings().getJQueryReference())));
+        response.render(CssHeaderItem.forReference(FontAwesomeCssReference.instance()));
+        response.render(CssHeaderItem.forReference(new BootstrapOverridesCssResourceReference()));
+        contributeThemeSpecificOverrides(response);
+
+        response.render(JavaScriptReferenceHeaderItem.forReference(JQUERY_LIVEQUERY_JS));
+        response.render(JavaScriptReferenceHeaderItem.forReference(JQUERY_ISIS_WICKET_VIEWER_JS));
+
+        final JGrowlBehaviour jGrowlBehaviour = new JGrowlBehaviour();
+        jGrowlBehaviour.renderFeedbackMessages(response);
+
+        if(applicationCss != null) {
+            response.render(CssReferenceHeaderItem.forUrl(applicationCss));
+        }
+        if(applicationJs != null) {
+            response.render(JavaScriptReferenceHeaderItem.forUrl(applicationJs));
+        }
+
+        if(isModernBrowser()) {
+            addBootLint(response);
+        }
+    }
+
+    private void addBootLint(final IHeaderResponse response) {
+        response.render(JavaScriptHeaderItem.forReference(BootlintJavaScriptReference.INSTANCE));
+    }
+
+    private boolean isModernBrowser() {
+        return !isIePre9();
+    }
+
+    private boolean isIePre9() {
+        final WebClientInfo clientInfo = WebSession.get().getClientInfo();
+        final ClientProperties properties = clientInfo.getProperties();
+        if (properties.isBrowserInternetExplorer())
+            if (properties.getBrowserVersionMajor() < 9)
+                return true;
+        return false;
+    }
+
+    /**
+     * Contributes theme specific Bootstrap CSS overrides if there is such resource
+     *
+     * @param response The header response to contribute to
+     */
+    private void contributeThemeSpecificOverrides(final IHeaderResponse response) {
+        final IBootstrapSettings bootstrapSettings = Bootstrap.getSettings(getApplication());
+        final ITheme activeTheme = bootstrapSettings.getActiveThemeProvider().getActiveTheme();
+        final String name = activeTheme.name().toLowerCase(Locale.ENGLISH);
+        final String themeSpecificOverride = "bootstrap-overrides-" + name + ".css";
+        final ResourceReference.Key themeSpecificOverrideKey = new ResourceReference.Key(PageAbstract.class.getName(), themeSpecificOverride, null, null, null);
+        if (PackageResource.exists(themeSpecificOverrideKey)) {
+            response.render(CssHeaderItem.forReference(new CssResourceReference(themeSpecificOverrideKey)));
+        }
+    }
+
+    /**
+     * As provided in the {@link #PageAbstract(org.apache.wicket.request.mapper.parameter.PageParameters, String, org.apache.isis.viewer.wicket.ui.ComponentType...)} constructor}.
+     * 
+     * <p>
+     * This superclass doesn't do anything with this property directly, but
+     * requiring it to be provided enforces standardization of the
+     * implementation of the subclasses.
+     */
+    public List<ComponentType> getChildModelTypes() {
+        return childComponentIds;
+    }
+
+    /**
+     * For subclasses to call.
+     * 
+     * <p>
+     * Should be called in the subclass' constructor.
+     * 
+     * @param model
+     *            - used to find the best matching {@link ComponentFactory} to
+     *            render the model.
+     */
+    protected void addChildComponents(final MarkupContainer container, final IModel<?> model) {
+        for (final ComponentType componentType : getChildModelTypes()) {
+            addComponent(container, componentType, model);
+        }
+    }
+
+    private void addComponent(final MarkupContainer container, final ComponentType componentType, final IModel<?> model) {
+        getComponentFactoryRegistry().addOrReplaceComponent(container, componentType, model);
+    }
+
+
+    ////////////////////////////////////////////////////////////////
+    // bookmarked pages
+    ////////////////////////////////////////////////////////////////
+
+    /**
+     * Convenience for subclasses
+     */
+    protected void addBookmarkedPages(final MarkupContainer container) {
+        Component bookmarks = getComponentFactoryRegistry().createComponent(ComponentType.BOOKMARKED_PAGES, ID_BOOKMARKED_PAGES, getBookmarkedPagesModel());
+        container.add(bookmarks);
+        bookmarks.add(new Behavior() {
+            @Override
+            public void onConfigure(Component component) {
+                super.onConfigure(component);
+
+                PageParameters parameters = getPageParameters();
+                component.setVisible(parameters.get(PageParametersUtils.ISIS_NO_HEADER_PARAMETER_NAME).isNull());
+            }
+        });
+    }
+
+    protected void bookmarkPage(final BookmarkableModel<?> model) {
+        getBookmarkedPagesModel().bookmarkPage(model);
+    }
+
+    protected void removeAnyBookmark(final EntityModel model) {
+        getBookmarkedPagesModel().remove(model);
+    }
+
+    private BookmarkedPagesModel getBookmarkedPagesModel() {
+        final BookmarkedPagesModelProvider session = (BookmarkedPagesModelProvider) getSession();
+        return session.getBookmarkedPagesModel();
+    }
+
+
+
+    // ///////////////////////////////////////////////////////////////////
+    // ActionPromptModalWindowProvider
+    // ///////////////////////////////////////////////////////////////////
+    
+    private ActionPromptModalWindow actionPromptModalWindow;
+
+    public ActionPrompt getActionPrompt() {
+        return ActionPromptModalWindow.getActionPromptModalWindowIfEnabled(actionPromptModalWindow);
+    }
+
+    private void addActionPromptModalWindow(final MarkupContainer parent) {
+        actionPromptModalWindow = ActionPromptModalWindow.newModalWindow(ID_ACTION_PROMPT_MODAL_WINDOW); 
+        parent.addOrReplace(actionPromptModalWindow);
+    }
+
+    
+    // ///////////////////////////////////////////////////////////////////
+    // UI Hint
+    // ///////////////////////////////////////////////////////////////////
+
+    /**
+     * Propagates all {@link org.apache.isis.viewer.wicket.model.hints.IsisEventLetterAbstract letter} events down to
+     * all child components, wrapped in an {@link org.apache.isis.viewer.wicket.model.hints.IsisEnvelopeEvent envelope} event.
+     */
+    public void onEvent(final org.apache.wicket.event.IEvent<?> event) {
+        final Object payload = event.getPayload();
+        if(payload instanceof IsisEventLetterAbstract) {
+            final IsisEventLetterAbstract letter = (IsisEventLetterAbstract)payload;
+            final IsisEnvelopeEvent broadcastEv = new IsisEnvelopeEvent(letter);
+            send(this, Broadcast.BREADTH, broadcastEv);
+        }
+    }
+    
+
+    // ///////////////////////////////////////////////////////////////////
+    // Convenience
+    // ///////////////////////////////////////////////////////////////////
+
+    protected ComponentFactoryRegistry getComponentFactoryRegistry() {
+        final ComponentFactoryRegistryAccessor cfra = (ComponentFactoryRegistryAccessor) getApplication();
+        return cfra.getComponentFactoryRegistry();
+    }
+
+    protected <T> T lookupService(final Class<T> serviceClass) {
+        return getServicesInjector().lookupService(serviceClass);
+    }
+
+    // ///////////////////////////////////////////////////
+    // System components
+    // ///////////////////////////////////////////////////
+
+    protected ServicesInjectorSpi getServicesInjector() {
+        return getPersistenceSession().getServicesInjector();
+    }
+
+    protected PersistenceSession getPersistenceSession() {
+        return IsisContext.getPersistenceSession();
+    }
+
+    protected SpecificationLoaderSpi getSpecificationLoader() {
+        return IsisContext.getSpecificationLoader();
+    }
+    
+    protected AuthenticationSession getAuthenticationSession() {
+        return IsisContext.getAuthenticationSession();
+    }
+    
+    protected MessageBroker getMessageBroker() {
+        return IsisContext.getMessageBroker();
+    }
+    
+    protected IsisConfiguration getConfiguration() {
+        return IsisContext.getConfiguration();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.properties
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.properties b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.properties
new file mode 100644
index 0000000..488c223
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.properties
@@ -0,0 +1,21 @@
+#
+#  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.
+#
+
+aboutLabel=About
+logoutLabel=Logout

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageClassList.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageClassList.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageClassList.java
new file mode 100644
index 0000000..0e0b459
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageClassList.java
@@ -0,0 +1,40 @@
+/*
+ *  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.wicket.ui.pages;
+
+import java.io.Serializable;
+
+import org.apache.isis.viewer.wicket.model.models.PageType;
+
+/**
+ * Specify the pages to use for each {@link PageType}.
+ * 
+ * <p>
+ * The <tt>PageClassListDefault</tt> default implementation returns pages that
+ * all inherit from {@link PageAbstract} and which (by component markup
+ * inheritance) therefore define a set of CSS, along with the overall layout of
+ * each pages. This interface can be used to selectively replace some or all of
+ * these pages.
+ */
+public interface PageClassList extends Serializable {
+
+    void registerPages(PageClassRegistrySpi pageRegistry);
+
+}