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 2014/11/13 18:12:40 UTC

[1/7] isis git commit: ISIS-948: refactoring the EventBusService (and EventBusServiceDefault subclass) so that the guava EventBus is actually instantiated only once, in effect with singleton scope, rather than with request-scoped.

Repository: isis
Updated Branches:
  refs/heads/master d15cec100 -> f436bcb12


ISIS-948: refactoring the EventBusService (and EventBusServiceDefault subclass) so that the guava EventBus is actually instantiated only once, in effect with singleton scope, rather than with request-scoped.

Although this design is not symmetrical (in the various @PostConstruct and @PreDestroy lifecycle methods), it has many fewer moving parts.  Making the guava EventBus a singleton means that all events flow through this single instance.  We rely on the proxies of the request-scoped services to dispatch to the appropriate instance on the thread (assuming that a transaction is running for that thread).


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/55fc862f
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/55fc862f
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/55fc862f

Branch: refs/heads/master
Commit: 55fc862fdeb2e743a9baa5279a32dd853c089790
Parents: d15cec1
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Thu Nov 13 12:15:56 2014 +0000
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Thu Nov 13 12:15:56 2014 +0000

----------------------------------------------------------------------
 .../services/eventbus/EventBusService.java      | 222 ++++++++++++++++---
 .../eventbus/EventBusServiceDefault.java        |  42 +++-
 .../eventbus/RequestScopedEventBus.java         | 186 ----------------
 .../service/eventbus/EventBusServiceJdo.java    |   1 -
 4 files changed, 221 insertions(+), 230 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/55fc862f/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/EventBusService.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/EventBusService.java b/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/EventBusService.java
index 8325e8a..44287e7 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/EventBusService.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/EventBusService.java
@@ -16,11 +16,10 @@
  */
 package org.apache.isis.applib.services.eventbus;
 
-import java.util.Map;
+import java.util.Collections;
 import java.util.Set;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.atomic.AtomicInteger;
-import com.google.common.collect.MapMaker;
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
 import com.google.common.collect.Sets;
 import com.google.common.eventbus.EventBus;
 import org.apache.isis.applib.annotation.Hidden;
@@ -50,64 +49,145 @@ public abstract class EventBusService {
      */
     public static class Noop extends EventBusService {
         @Override
-        public void register(Object domainObject) {}
+        public void register(Object domainService) {}
         @Override
-        public void unregister(Object domainObject) {}
+        public void unregister(Object domainService) {}
         @Override
         public void post(Object event) {}
         @Override
         protected EventBus getEventBus() {
             return null;
         }
+        @Override
+        protected EventBus newEventBus() { return null; }
     }
 
     public static final EventBusService NOOP = new Noop();
-    
+
+    //region > init, shutdown
+
     /**
-     * @return an {@link EventBus} scoped to the current session.
+     * Cannot do the setup of the event bus here (so this is asymmetric with <code>@PreDestroy</code>) because there is
+     * no guarantee of the order in which <code>@PostConstruct</code> is called on any request-scoped services.  We
+     * therefore allow all services (singleton or request-scoped) to {@link #register(Object) register} themselves
+     * with this service in their <code>@PostConstruct</code> and do the actual instantiation of the guava
+     * {@link com.google.common.eventbus.EventBus} and registering of subscribers lazily, in {@link #getEventBus()}.
+     * This lifecycle method ({@link #init()}) is therefore a no-op.
+     *
+     * <p>
+     *     The guava {@link com.google.common.eventbus.EventBus} can however (and is) be torndown in the
+     *     <code>@PreDestroy</code> {@link #shutdown()} lifecycle method.
+     * </p>
      */
     @Programmatic
-    protected abstract EventBus getEventBus();
+    @PostConstruct
+    public void init() {
+        // no-op
+    }
+
+    @Programmatic
+    @PreDestroy
+    public void shutdown() {
+        teardownEventBus();
+    }
+
+    //endregion
 
     //region > register, unregister
 
+    /**
+     * Both singleton and request-scoped domain services can register on the event bus; this should be done in their
+     * <code>@PostConstruct</code> callback method.
+     *
+     * <p>
+     *     <b>Important:</b> Request-scoped services should register their proxy, not themselves.  This is because it is
+     *     the responsibility of the proxy to ensure that the correct underlying (thread-local) instance of the service
+     *     is delegated to.  If the actual instance were to be registered, this would cause a memory leak and all sorts
+     *     of other unexpected issues.
+     * </p>
+     *
+     * <p>
+     *     Also, request-scoped services should <i>NOT</i> unregister themselves.  This is because the
+     *     <code>@PreDestroy</code> lifecycle method is called at the end of each transaction.  The proxy needs to
+     *     remain registered on behalf for any subsequent transactions.
+     * </p>
+     *
+     * <p>For example:</p>
+     * <pre>
+     *     @RequestScoped @DomainService
+     *     public class SomeSubscribingService {
+     *
+     *         private EventBusService ebs;
+     *         public void injectBus(EventBusService ebs) { this.ebs = ebs; }
+     *
+     *         private SomeSubscribingService proxy;
+     *         public void injectProxy(SomeSubscribingService proxy) { this.proxy = proxy; }
+     *
+     *         @PostConstruct
+     *         public void startRequest() {
+     *              // register with bus
+     *              ebs.register(proxy);
+     *         }
+     *         @PreDestroy
+     *         public void endRequest() {
+     *              //no-op
+     *         }
+     *     }
+     * </pre>
+     *
+     * <p>
+     *     The <code>@PostConstruct</code> callback is the correct place to register for both singleton and
+     *     request-scoped services.  For singleton domain services, this is done during the initial bootstrapping of
+     *     the system.  For request-scoped services, this is done for the first transaction.  In fact, because
+     *     singleton domain services are initialized <i>within a current transaction</i>, the request-scoped services
+     *     will actually be registered <i>before</i> the singleton services.  Each subsequent transaction will have the
+     *     request-scoped service re-register with the event bus, however the event bus stores its subscribers in a
+     *     set and so these re-registrations are basically a no-op.
+     * </p>
+     *
+     * @param domainService
+     */
     @Programmatic
-    public void register(Object domainObject) {
-        referenceCountBySubscriber.putIfAbsent(domainObject, new AtomicInteger(0));
-        referenceCountBySubscriber.get(domainObject).incrementAndGet();
+    public void register(final Object domainService) {
+        if(eventBus != null) {
+            throw new IllegalStateException("Event bus has already been created; too late to register any further subscribers");
+        }
+        subscribers.add(domainService);
     }
 
+    /**
+     * Notionally allows subscribers to unregister from the event bus; however this is a no-op.
+     *
+     * <p>
+     *     It is safe for singleton services to unregister from the bus, however this is only ever called when the
+     *     app is being shutdown so there is no real effect.  For request-scoped services meanwhile that (as
+     *     explained in {@link #register(Object)}'s documentation) actually register their proxy, it would be an error
+     *     to unregister the proxy; subsequent transactions (for this thread or others) must be routed through that
+     *     proxy.
+     * </p>
+     */
     @Programmatic
-    public void unregister(Object domainObject) {
-        final AtomicInteger atomicInteger = referenceCountBySubscriber.get(domainObject);
-        atomicInteger.decrementAndGet();
+    public void unregister(final Object domainService) {
+        // intentionally no-op
     }
 
     //endregion
 
     //region > subscribers
 
-    private final ConcurrentMap<Object, AtomicInteger> referenceCountBySubscriber = new MapMaker().weakKeys().makeMap();
+    private final Set<Object> subscribers = Sets.newConcurrentHashSet();
 
     /**
-     * Not API
+     * Returns an immutable snapshot of the current subscribers.
      */
     @Programmatic
     public Set<Object> getSubscribers() {
-
-
-
-        // only those subscribers that are "active"
-        Set<Object> subscribers = Sets.newLinkedHashSet();
-        for (Map.Entry<Object, AtomicInteger> subscriberReferenceCount : this.referenceCountBySubscriber.entrySet()) {
-            if(subscriberReferenceCount.getValue().get()>0) {
-                subscribers.add(subscriberReferenceCount.getKey());
-            }
-        }
-        return subscribers;
+        return Collections.unmodifiableSet(Sets.newLinkedHashSet(subscribers));
     }
     //endregion
-    
+
+    //region > post
+
     /**
      * Post an event.
      */
@@ -119,10 +199,86 @@ public abstract class EventBusService {
         getEventBus().post(event);
     }
 
-    
+    //endregion
+
+
+    //region > getEventBus
+
+    /**
+     * Lazily populated in {@link #getEventBus()}.
+     */
+    private EventBus eventBus;
+
+    /**
+     * Lazily populates the event bus for the current {@link #getSubscribers() subscribers}.
+     */
+    @Programmatic
+    protected EventBus getEventBus() {
+        setupEventBus();
+        return eventBus;
+    }
+
+    /**
+     * Set of subscribers registered with the event bus.
+     *
+     * <p>
+     * Lazily populated in {@link #setupEventBus()}.
+     * </p>
+     */
+    private Set<Object> registeredSubscribers;
+
+    /**
+     * Populates {@link #eventBus} with the {@link #registeredSubscribers currently registered subscribers}.
+     *
+     * <p>
+     *     Guava event bus will throw an exception if attempt to unsubscribe any subscribers that were not subscribed.
+     *     It is therefore the responsibility of this service to remember which services were registered
+     *     at the start of the request, and to unregister precisely this same set of services at the end.
+     * </p>
+     *
+     * <p>
+     *     That said, the Guava event bus is only ever instantiated once (it is in essence an application-scoped singleton),
+     *     and so once created it is not possible for any subscribers to be registered.  For this reason, the
+     *     {@link #register(Object)} will throw an exception if any attempt is made to register once the event bus
+     *     has been instantiated.
+     * </p>
+     */
+    protected void setupEventBus() {
+        if(eventBus != null) {
+            return;
+        }
+        this.eventBus = newEventBus();
+
+        registeredSubscribers = getSubscribers();
+
+        for (Object subscriber : this.registeredSubscribers) {
+            eventBus.register(subscriber);
+        }
+    }
+
+    protected void teardownEventBus() {
+        if(registeredSubscribers != null) {
+            for (Object subscriber : this.registeredSubscribers) {
+                eventBus.unregister(subscriber);
+            }
+        }
+
+        this.eventBus = null;
+    }
+
+    //endregion
+
+    //region > hook methods (newEventBus, skip)
+
+    /**
+     * Mandatory hook method for subclass to instantiate an appropriately configured Guava event bus.
+     */
+    protected abstract EventBus newEventBus();
+
+
     /**
      * A hook to allow subclass implementations to skip the publication of certain events.
-     * 
+     *
      * <p>
      * For example, the <tt>EventBusServiceJdo</tt> does not publish events if the method
      * is called by JDO/DataNucleus infrastructure, eg during hydration or commits.
@@ -130,5 +286,9 @@ public abstract class EventBusService {
     protected boolean skip(Object event) {
         return false;
     }
+
+    //endregion
+
+
 }
 

http://git-wip-us.apache.org/repos/asf/isis/blob/55fc862f/core/runtime/src/main/java/org/apache/isis/core/runtime/services/eventbus/EventBusServiceDefault.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/eventbus/EventBusServiceDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/eventbus/EventBusServiceDefault.java
index 3e5e5f9..8733456 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/eventbus/EventBusServiceDefault.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/eventbus/EventBusServiceDefault.java
@@ -16,10 +16,17 @@
  */
 package org.apache.isis.core.runtime.services.eventbus;
 
-import javax.inject.Inject;
+import java.util.List;
+import com.google.common.base.Throwables;
 import com.google.common.eventbus.EventBus;
-import org.apache.isis.applib.annotation.Programmatic;
+import com.google.common.eventbus.SubscriberExceptionContext;
+import com.google.common.eventbus.SubscriberExceptionHandler;
+import org.apache.isis.applib.NonRecoverableException;
+import org.apache.isis.applib.RecoverableException;
 import org.apache.isis.applib.services.eventbus.EventBusService;
+import org.apache.isis.core.commons.exceptions.IsisApplicationException;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager;
 
 /**
  * @deprecated - because <tt>EventBusServiceJdo</tt> is annotated as the default implementation.
@@ -27,22 +34,33 @@ import org.apache.isis.applib.services.eventbus.EventBusService;
 @Deprecated
 public class EventBusServiceDefault extends EventBusService {
 
-    //region > API (getEventBus)
-
-    @Programmatic
     @Override
-    protected EventBus getEventBus() {
-        return requestScopedEventBus.getEventBus();
+    protected EventBus newEventBus() {
+        return new EventBus(newEventBusSubscriberExceptionHandler());
     }
-    //endregion
-
 
-    //region > injected services
+    protected SubscriberExceptionHandler newEventBusSubscriberExceptionHandler() {
+        return new SubscriberExceptionHandler(){
+            @Override
+            public void handleException(Throwable exception, SubscriberExceptionContext context) {
+                final List<Throwable> causalChain = Throwables.getCausalChain(exception);
+                for (Throwable cause : causalChain) {
+                    if(cause instanceof RecoverableException || cause instanceof NonRecoverableException) {
+                        getTransactionManager().getTransaction().setAbortCause(new IsisApplicationException(exception));
+                        return;
+                    }
+                }
+                // otherwise simply ignore
+            }
+        };
+    }
 
-    @Inject
-    private RequestScopedEventBus requestScopedEventBus;
+    protected IsisTransactionManager getTransactionManager() {
+        return IsisContext.getTransactionManager();
+    }
 
     //endregion
 
+
 }
 

http://git-wip-us.apache.org/repos/asf/isis/blob/55fc862f/core/runtime/src/main/java/org/apache/isis/core/runtime/services/eventbus/RequestScopedEventBus.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/eventbus/RequestScopedEventBus.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/eventbus/RequestScopedEventBus.java
deleted file mode 100644
index 39b12ba..0000000
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/eventbus/RequestScopedEventBus.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/**
- *  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.core.runtime.services.eventbus;
-
-import java.util.List;
-import java.util.Set;
-import javax.annotation.PostConstruct;
-import javax.annotation.PreDestroy;
-import javax.enterprise.context.RequestScoped;
-import com.google.common.base.Throwables;
-import com.google.common.eventbus.EventBus;
-import com.google.common.eventbus.SubscriberExceptionContext;
-import com.google.common.eventbus.SubscriberExceptionHandler;
-import org.apache.isis.applib.NonRecoverableException;
-import org.apache.isis.applib.RecoverableException;
-import org.apache.isis.applib.annotation.DomainService;
-import org.apache.isis.applib.annotation.Programmatic;
-import org.apache.isis.core.commons.exceptions.IsisApplicationException;
-import org.apache.isis.core.runtime.system.context.IsisContext;
-import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager;
-
-
-/**
- * Manages a guava {@link com.google.common.eventbus.EventBus}, scoped and private to a given request.
- *
- * <p>
- *     This involves obtaining the set of subscribers from the (owning, singleton) {@link org.apache.isis.applib.services.eventbus.EventBusService event bus service}
- *     and registering/unregistering them with the guava event bus at the appropriate time.
- * </p>
- */
-@DomainService
-@RequestScoped
-public class RequestScopedEventBus {
-
-    //region > startRequest, endRequest
-
-    /**
-     * Cannot do the setup of the event bus here (so this is asymmetric with <code>@PreDestroy</code>) because there is
-     * no guarantee of the order in which <code>@PostConstruct</code> is called on any request-scoped services.  We
-     * therefore let the request-scoped services register themselves with (the owning, singleton)
-     * {@link org.apache.isis.applib.services.eventbus.EventBusService} in their
-     * <code>@PostConstruct</code> and do the actual instantiation of the guava {@link com.google.common.eventbus.EventBus}
-     * and registering of subscribers lazily, in {@link #getEventBus()}.  This lifecycle method ({@link #startRequest()})
-     * is therefore a no-op.
-     *
-     * <p>
-     *     The guava {@link com.google.common.eventbus.EventBus} can however (and is) be torndown in the
-     *     <code>@PreDestroy</code> {@link #endRequest()} lifecycle method.
-     * </p>
-     */
-    @Programmatic
-    @PostConstruct
-    public void startRequest() {
-        // no-op
-    }
-
-    @Programmatic
-    @PreDestroy
-    public void endRequest() {
-        teardownEventBus();
-    }
-
-    //endregion
-
-    //region > (guava) event bus
-
-    /**
-     * Lazily populated in {@link #getEventBus()}.
-     */
-    private EventBus eventBus;
-
-    /**
-     * Lazily populates the event bus and captures the set of subscribers from
-     * {@link EventBusServiceDefault#getSubscribers()}}.
-     *
-     * <p>
-     *     These are torn down when the {@link #endRequest() request ends}.
-     * </p>
-     */
-    @Programmatic
-    public EventBus getEventBus() {
-        setupEventBus();
-        return eventBus;
-    }
-
-    /**
-     * Set of subscribers registered with the event bus.
-     *
-     * <p>
-     * Lazily populated in {@link #setupEventBus()}.
-     * </p>
-     */
-    private Set<Object> subscribers;
-
-    /**
-     * Populates {@link #eventBus} and {@link #subscribers}.
-     *
-     * <p>
-     *     Guava event bus will throw an exception if attempt to unsubscribe any subscribers that were not subscribed.
-     *     It is therefore the responsibility of this (wrapper) service to remember which services were registered
-     *     at the start of the request, and to unregister precisely this same set of services at the end.
-     * </p>
-     */
-    protected void setupEventBus() {
-        if(eventBus != null) {
-            return;
-        }
-        this.eventBus = newEventBus();
-
-        // "pulls" subscribers from the (singleton) event bus service
-        subscribers = eventBusService.getSubscribers();
-
-        for (Object subscriber : this.subscribers) {
-            eventBus.register(subscriber);
-        }
-    }
-
-    protected void teardownEventBus() {
-        if(subscribers != null) {
-            for (Object subscriber : this.subscribers) {
-                eventBus.unregister(subscriber);
-            }
-        }
-
-        this.eventBus = null;
-    }
-
-    protected EventBus newEventBus() {
-        return new EventBus(newEventBusSubscriberExceptionHandler());
-    }
-
-    protected SubscriberExceptionHandler newEventBusSubscriberExceptionHandler() {
-        return new SubscriberExceptionHandler(){
-            @Override
-            public void handleException(Throwable exception, SubscriberExceptionContext context) {
-                final List<Throwable> causalChain = Throwables.getCausalChain(exception);
-                for (Throwable cause : causalChain) {
-                    if(cause instanceof RecoverableException || cause instanceof NonRecoverableException) {
-                        getTransactionManager().getTransaction().setAbortCause(new IsisApplicationException(exception));
-                        return;
-                    }
-                }
-                // otherwise simply ignore
-            }
-        };
-    }
-
-    protected IsisTransactionManager getTransactionManager() {
-        return IsisContext.getTransactionManager();
-    }
-
-    //endregion
-
-    //region > injected services
-    private EventBusServiceDefault eventBusService;
-    /**
-     * Singleton holding the list of subscribers.
-     *
-     * <p>
-     *     Must use an <code>injectXxx</code> method because Isis does not (currently) support field injection into
-     *     request-scoped services (the javassist proxy does not delegate on).
-     * </p>
-     */
-    @Programmatic
-    public void injectEventBusService(EventBusServiceDefault eventBusService) {
-        this.eventBusService = eventBusService;
-    }
-
-    //endregion
-
-}
-

http://git-wip-us.apache.org/repos/asf/isis/blob/55fc862f/core/runtime/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/service/eventbus/EventBusServiceJdo.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/service/eventbus/EventBusServiceJdo.java b/core/runtime/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/service/eventbus/EventBusServiceJdo.java
index ba59510..a7f4c64 100644
--- a/core/runtime/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/service/eventbus/EventBusServiceJdo.java
+++ b/core/runtime/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/service/eventbus/EventBusServiceJdo.java
@@ -38,7 +38,6 @@ import org.apache.isis.objectstore.jdo.datanucleus.JDOStateManagerForIsis.Hint;
 @DomainService
 public class EventBusServiceJdo extends EventBusServiceDefault {
 
-
     /**
      * skip if called in any way by way of the {@link JDOStateManagerForIsis}.
      * 


[3/7] isis git commit: ISIS-537: updates to javadoc only,

Posted by da...@apache.org.
ISIS-537: updates to javadoc only,


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/3f918025
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/3f918025
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/3f918025

Branch: refs/heads/master
Commit: 3f918025a3fdae1a13692ed06123091a941354cc
Parents: c8485ac
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Thu Nov 13 13:46:39 2014 +0000
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Thu Nov 13 13:46:39 2014 +0000

----------------------------------------------------------------------
 .../viewer/wicket/model/common/OnConcurrencyExceptionHandler.java | 3 ++-
 .../isis/viewer/wicket/model/common/OnSelectionHandler.java       | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/3f918025/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/common/OnConcurrencyExceptionHandler.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/common/OnConcurrencyExceptionHandler.java b/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/common/OnConcurrencyExceptionHandler.java
index 6b11659..71b1e71 100644
--- a/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/common/OnConcurrencyExceptionHandler.java
+++ b/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/common/OnConcurrencyExceptionHandler.java
@@ -26,7 +26,8 @@ import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.adapter.version.ConcurrencyException;
 
 /**
- * Decouples the mechanism for selecting entities returned in collections.
+ * Decouples the handling of concurrency exceptions when a bulk action is invoked (between the toggle box column and the
+ * standalone collection panel).
  */
 public interface OnConcurrencyExceptionHandler extends Serializable {
 

http://git-wip-us.apache.org/repos/asf/isis/blob/3f918025/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/common/OnSelectionHandler.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/common/OnSelectionHandler.java b/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/common/OnSelectionHandler.java
index 17e1d4d..491c144 100644
--- a/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/common/OnSelectionHandler.java
+++ b/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/common/OnSelectionHandler.java
@@ -25,7 +25,8 @@ import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 
 /**
- * Decouples the mechanism for selecting entities returned in collections.
+ * Decouples the selecting of items (by a toggle box column) from the invocation of bulk actions on those items (by
+ * the standalone collection panel).
  */
 public interface OnSelectionHandler extends Serializable {
 


[2/7] isis git commit: ISIS-948: adding validation to EventBusService#register method (in EventBusServiceDefault subclass, in fact); improvements to javadoc.

Posted by da...@apache.org.
ISIS-948: adding validation to EventBusService#register method (in EventBusServiceDefault subclass, in fact); improvements to javadoc.


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/c8485acd
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/c8485acd
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/c8485acd

Branch: refs/heads/master
Commit: c8485acd75522815529826393d7daa8d1823de4e
Parents: 55fc862
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Thu Nov 13 12:38:30 2014 +0000
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Thu Nov 13 12:38:30 2014 +0000

----------------------------------------------------------------------
 .../services/eventbus/EventBusService.java      | 21 ++++++-----
 .../eventbus/EventBusServiceDefault.java        | 37 +++++++++++++++++++-
 2 files changed, 48 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/c8485acd/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/EventBusService.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/EventBusService.java b/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/EventBusService.java
index 44287e7..8a994a1 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/EventBusService.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/EventBusService.java
@@ -33,6 +33,7 @@ import org.apache.isis.applib.annotation.Programmatic;
  *      Only domain services (not domain entities or view models) should be registered; only they are guaranteed to be
  *      instantiated and resident in memory.
  * </p>
+ *
  * <p>
  *     It <i>is</i> possible to register request-scoped services, however they should register their proxy
  *     rather than themselves.  This ensures that the actual subscribers are all singletons.  This implementation uses
@@ -117,11 +118,8 @@ public abstract class EventBusService {
      *     @RequestScoped @DomainService
      *     public class SomeSubscribingService {
      *
-     *         private EventBusService ebs;
-     *         public void injectBus(EventBusService ebs) { this.ebs = ebs; }
-     *
-     *         private SomeSubscribingService proxy;
-     *         public void injectProxy(SomeSubscribingService proxy) { this.proxy = proxy; }
+     *         @Inject private EventBusService ebs;
+     *         @Inject private SomeSubscribingService proxy;
      *
      *         @PostConstruct
      *         public void startRequest() {
@@ -149,9 +147,14 @@ public abstract class EventBusService {
      */
     @Programmatic
     public void register(final Object domainService) {
-        if(eventBus != null) {
-            throw new IllegalStateException("Event bus has already been created; too late to register any further subscribers");
-        }
+        doRegister(domainService);
+    }
+
+    /**
+     * Extracted out only to make it easier for subclasses to override {@link #register(Object)} if there were ever a
+     * need to.
+     */
+    protected void doRegister(Object domainService) {
         subscribers.add(domainService);
     }
 
@@ -207,7 +210,7 @@ public abstract class EventBusService {
     /**
      * Lazily populated in {@link #getEventBus()}.
      */
-    private EventBus eventBus;
+    protected EventBus eventBus;
 
     /**
      * Lazily populates the event bus for the current {@link #getSubscribers() subscribers}.

http://git-wip-us.apache.org/repos/asf/isis/blob/c8485acd/core/runtime/src/main/java/org/apache/isis/core/runtime/services/eventbus/EventBusServiceDefault.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/eventbus/EventBusServiceDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/eventbus/EventBusServiceDefault.java
index 8733456..cd3626c 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/eventbus/EventBusServiceDefault.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/eventbus/EventBusServiceDefault.java
@@ -17,6 +17,7 @@
 package org.apache.isis.core.runtime.services.eventbus;
 
 import java.util.List;
+import javax.enterprise.context.RequestScoped;
 import com.google.common.base.Throwables;
 import com.google.common.eventbus.EventBus;
 import com.google.common.eventbus.SubscriberExceptionContext;
@@ -25,15 +26,49 @@ import org.apache.isis.applib.NonRecoverableException;
 import org.apache.isis.applib.RecoverableException;
 import org.apache.isis.applib.services.eventbus.EventBusService;
 import org.apache.isis.core.commons.exceptions.IsisApplicationException;
+import org.apache.isis.core.metamodel.facets.Annotations;
+import org.apache.isis.core.runtime.services.RequestScopedService;
 import org.apache.isis.core.runtime.system.context.IsisContext;
 import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager;
 
 /**
- * @deprecated - because <tt>EventBusServiceJdo</tt> is annotated as the default implementation.
+ * @deprecated - but only because {@link org.apache.isis.objectstore.jdo.datanucleus.service.eventbus.EventBusServiceJdo}
+ * is annotated (with <code>@DomainService</code>) as the default implementation.  The functionality in this implementation
+ * is still required.
  */
 @Deprecated
 public class EventBusServiceDefault extends EventBusService {
 
+    /**
+     * {@inheritDoc}
+     *
+     * This service overrides the method to perform additional validation that (a) request-scoped services register
+     * their proxies, not themselves, and (b) that singleton services are never registered after the event bus has
+     * been created.
+     *
+     * <p>
+     *     Note that we <i>do</i> allow for request-scoped services to register (their proxies) multiple times, ie at
+     *     the beginning of each transaction.  Because the subscribers are stored in a set, these additional
+     *     registrations are in effect ignored.
+     * </p>
+     */
+    @Override
+    public void register(final Object domainService) {
+        if(domainService instanceof RequestScopedService) {
+            // ok; allow to be registered multiple times (each xactn) since stored in a set.
+        } else {
+            if (Annotations.getAnnotation(domainService.getClass(), RequestScoped.class) != null) {
+                throw new IllegalArgumentException("Request-scoped services must register their proxy, not themselves");
+            }
+            // a singleton
+            if(eventBus != null) {
+                // ... coming too late to the party.
+                throw new IllegalStateException("Event bus has already been created; too late to register any further (singleton) subscribers");
+            }
+        }
+        super.register(domainService);
+    }
+
     @Override
     protected EventBus newEventBus() {
         return new EventBus(newEventBusSubscriberExceptionHandler());


[6/7] isis git commit: ISIS-928: removing very old commented out code in JavassistImposteriser.

Posted by da...@apache.org.
ISIS-928: removing very old commented out code in JavassistImposteriser.


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/ad8e6b88
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/ad8e6b88
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/ad8e6b88

Branch: refs/heads/master
Commit: ad8e6b8838571791c16b5551f78a78718449a929
Parents: 235c18f
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Thu Nov 13 14:25:18 2014 +0000
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Thu Nov 13 14:25:18 2014 +0000

----------------------------------------------------------------------
 .../jmocking/JavassistImposteriser.java         | 67 --------------------
 1 file changed, 67 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/ad8e6b88/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/jmocking/JavassistImposteriser.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/jmocking/JavassistImposteriser.java b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/jmocking/JavassistImposteriser.java
index df56eb5..fdeb449 100644
--- a/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/jmocking/JavassistImposteriser.java
+++ b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/jmocking/JavassistImposteriser.java
@@ -110,61 +110,9 @@ public class JavassistImposteriser implements Imposteriser {
         proxyFactory.setInterfaces(ancilliaryTypes);
 
         return proxyFactory.createClass();
-
-        // original cglib code:
-
-    //        final Enhancer enhancer = new Enhancer() {
-    //            @Override
-    //            @SuppressWarnings("unchecked")
-    //            protected void filterConstructors(Class sc, List constructors) {
-    //                // Don't filter
-    //            }
-    //        };
-    //        enhancer.setClassLoader(SearchingClassLoader.combineLoadersOf(mockedType, ancilliaryTypes));
-    //        enhancer.setUseFactory(true);
-    //        if (mockedType.isInterface()) {
-    //            enhancer.setSuperclass(Object.class);
-    //            enhancer.setInterfaces(prepend(mockedType, ancilliaryTypes));
-    //        }
-    //        else {
-    //            enhancer.setSuperclass(mockedType);
-    //            enhancer.setInterfaces(ancilliaryTypes);
-    //        }
-    //        enhancer.setCallbackTypes(new Class[]{InvocationHandler.class, NoOp.class});
-    //        enhancer.setCallbackFilter(IGNORE_BRIDGE_METHODS);
-    //        if (mockedType.getSigners() != null) {
-    //            enhancer.setNamingPolicy(NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES);
-    //        }
-    //
-    //        try {
-    //            return enhancer.createClass();
-    //        }
-    //        catch (CodeGenerationException e) {
-    //            // Note: I've only been able to manually test this.  It exists to help people writing
-    //            //       Eclipse plug-ins or using other environments that have sophisticated class loader
-    //            //       structures.
-    //            throw new IllegalArgumentException("could not imposterise " + mockedType, e);
-    //        }
-
     }
 
 
-    // original cglib code:
-
-    //    private static final NamingPolicy NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES = new DefaultNamingPolicy() {
-    //        @Override
-    //        public String getClassName(String prefix, String source, Object key, Predicate names) {
-    //            return "org.jmock.codegen." + super.getClassName(prefix, source, key, names);
-    //        }
-    //    };
-    //
-    //    private static final CallbackFilter IGNORE_BRIDGE_METHODS = new CallbackFilter() {
-    //        public int accept(Method method) {
-    //            return method.isBridge() ? 1 : 0;
-    //        }
-    //    };
-
-
     private Object proxy(Class<?> proxyClass, final Invokable mockObject) {
 
         final ProxyObject proxyObject = (ProxyObject) objenesis.newInstance(proxyClass);
@@ -176,19 +124,6 @@ public class JavassistImposteriser implements Imposteriser {
         });
 
         return proxyObject;
-
-        // original cglib code:
-
-        //        final Factory proxy = (Factory)objenesis.newInstance(proxyClass);
-        //        proxy.setCallbacks(new Callback[] {
-        //            new InvocationHandler() {
-        //                public Object invoke(Object receiver, Method method, Object[] args) throws Throwable {
-        //                    return mockObject.invoke(new Invocation(receiver, method, args));
-        //                }
-        //            },
-        //            NoOp.INSTANCE
-        //        });
-        //        return proxy;
     }
 
     private static Class<?>[] combine(Class<?> first, Class<?>... rest) {
@@ -197,6 +132,4 @@ public class JavassistImposteriser implements Imposteriser {
         System.arraycopy(rest, 0, all, 1, rest.length);
         return all;
     }
-    
-    //public static class ClassWithSuperclassToWorkAroundCglibBug {}
 }


[4/7] isis git commit: ISIS-537: removing unused/no-op code.

Posted by da...@apache.org.
ISIS-537: removing unused/no-op code.


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/41a723d9
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/41a723d9
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/41a723d9

Branch: refs/heads/master
Commit: 41a723d909f60c5f72d21ad56b0c2b639ee1abea
Parents: 3f91802
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Thu Nov 13 13:47:58 2014 +0000
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Thu Nov 13 13:47:58 2014 +0000

----------------------------------------------------------------------
 .../multiple/CollectionContentsMultipleViewsPanel.java       | 8 --------
 1 file changed, 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/41a723d9/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/multiple/CollectionContentsMultipleViewsPanel.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/multiple/CollectionContentsMultipleViewsPanel.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/multiple/CollectionContentsMultipleViewsPanel.java
index 9e60554..a6d5d9a 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/multiple/CollectionContentsMultipleViewsPanel.java
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/multiple/CollectionContentsMultipleViewsPanel.java
@@ -109,12 +109,6 @@ public class CollectionContentsMultipleViewsPanel
             this.addOrReplace(underlyingView);
         }
 
-        if(selectorDropdownPanelIfAny != null) {
-
-        } else {
-
-        }
-
         // hide any unused placeholders
         while(i<MAX_NUM_UNDERLYING_VIEWS) {
             String underlyingId = underlyingIdPrefix + "-" + i;
@@ -189,8 +183,6 @@ public class CollectionContentsMultipleViewsPanel
                 // ignore
             }
         }
-
-
     }
 
 


[5/7] isis git commit: ISIS-537: fixing a bug with ModelAbstract#clearHint, adding a unit test to cover.

Posted by da...@apache.org.
ISIS-537: fixing a bug with ModelAbstract#clearHint, adding a unit test to cover.


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/235c18f7
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/235c18f7
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/235c18f7

Branch: refs/heads/master
Commit: 235c18f701143e96ed385216619525b1798bf20e
Parents: 41a723d
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Thu Nov 13 14:23:39 2014 +0000
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Thu Nov 13 14:23:39 2014 +0000

----------------------------------------------------------------------
 component/viewer/wicket/model/pom.xml           |   5 +
 .../wicket/model/models/ModelAbstract.java      |   8 +-
 .../wicket/model/models/ModelAbstractTest.java  | 170 +++++++++++++++++++
 3 files changed, 177 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/235c18f7/component/viewer/wicket/model/pom.xml
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/model/pom.xml b/component/viewer/wicket/model/pom.xml
index d631017..f954ad5 100644
--- a/component/viewer/wicket/model/pom.xml
+++ b/component/viewer/wicket/model/pom.xml
@@ -65,6 +65,11 @@
 			<groupId>org.apache.isis.core</groupId>
 			<artifactId>isis-core-runtime</artifactId>
 		</dependency>
+		<dependency>
+			<groupId>org.apache.isis.core</groupId>
+			<artifactId>isis-core-unittestsupport</artifactId>
+			<scope>test</scope>
+		</dependency>
 
 	</dependencies>
 

http://git-wip-us.apache.org/repos/asf/isis/blob/235c18f7/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ModelAbstract.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ModelAbstract.java b/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ModelAbstract.java
index 8e65177..1ae3735 100644
--- a/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ModelAbstract.java
+++ b/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ModelAbstract.java
@@ -73,17 +73,13 @@ public abstract class ModelAbstract<T> extends LoadableDetachableModel<T> implem
         if(value != null) {
             hints.put(hintKey, value);
         } else {
-            clearHint(component, hintKey);
+            hints.remove(hintKey);
         }
     }
 
     @Override
     public void clearHint(Component component, String key) {
-        if(component == null) {
-            return;
-        }
-        String hintKey = hintKey(component, key);
-        hints.remove(hintKey);
+        setHint(component, key, null);
     }
 
 

http://git-wip-us.apache.org/repos/asf/isis/blob/235c18f7/component/viewer/wicket/model/src/test/java/org/apache/isis/viewer/wicket/model/models/ModelAbstractTest.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/model/src/test/java/org/apache/isis/viewer/wicket/model/models/ModelAbstractTest.java b/component/viewer/wicket/model/src/test/java/org/apache/isis/viewer/wicket/model/models/ModelAbstractTest.java
new file mode 100644
index 0000000..4655239
--- /dev/null
+++ b/component/viewer/wicket/model/src/test/java/org/apache/isis/viewer/wicket/model/models/ModelAbstractTest.java
@@ -0,0 +1,170 @@
+/**
+ *  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.model.models;
+
+import java.util.Map;
+import org.apache.wicket.Component;
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.model.IModel;
+import org.jmock.Expectations;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.apache.isis.core.unittestsupport.jmocking.JUnitRuleMockery2;
+import org.apache.isis.viewer.wicket.model.hints.UiHintPathSignificant;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+public class ModelAbstractTest {
+
+    @Rule
+    public JUnitRuleMockery2 context = JUnitRuleMockery2.createFor(JUnitRuleMockery2.Mode.INTERFACES_AND_CLASSES);
+
+    ModelAbstract<String> target;
+
+    static class UiHintPathSignificantComponent extends Component implements UiHintPathSignificant {
+        public UiHintPathSignificantComponent(String id) {
+            super(id);
+        }
+
+        public UiHintPathSignificantComponent(String id, IModel<?> model) {
+            super(id, model);
+        }
+
+        @Override
+        protected void onRender() {
+        }
+    }
+
+    static class UiHintPathSignificantMarkupContainer extends MarkupContainer implements UiHintPathSignificant {
+        public UiHintPathSignificantMarkupContainer(String id) {
+            super(id);
+        }
+
+        public UiHintPathSignificantMarkupContainer(String id, IModel<?> model) {
+            super(id, model);
+        }
+
+        @Override
+        protected void onRender() {
+        }
+    }
+
+    MarkupContainer mockParent;
+    Component mockComponent1;
+    Component mockComponent2;
+
+    @Before
+    public void setUp() throws Exception {
+        target = new ModelAbstract<String>("foo"){
+            @Override
+            protected String load() {
+                return null;
+            }
+        };
+
+        mockParent = context.mock(UiHintPathSignificantMarkupContainer.class, "parent");
+        mockComponent1 = context.mock(UiHintPathSignificantComponent.class, "component1");
+        mockComponent2 = context.mock(UiHintPathSignificantComponent.class, "component2");
+
+        context.checking(new Expectations() {{
+            allowing(mockParent).getId();
+            will(returnValue("parent"));
+
+            allowing(mockComponent1).getId();
+            will(returnValue("id1"));
+
+            allowing(mockComponent2).getId();
+            will(returnValue("id2"));
+
+            ignoring(mockComponent1);
+            ignoring(mockComponent2);
+
+        }});
+
+        mockComponent1.setParent(mockParent);
+        mockComponent2.setParent(mockParent);
+    }
+
+    public static class Hints extends ModelAbstractTest {
+
+        @Test
+        public void empty() throws Exception {
+            assertThat(target.getHint(mockComponent1, "key1"), is(nullValue()));
+        }
+
+        @Test
+        public void single() throws Exception {
+            target.setHint(mockComponent1, "key1", "value1");
+            assertThat(target.getHint(mockComponent1, "key1"), is("value1"));
+        }
+
+        @Test
+        public void clear() throws Exception {
+            target.setHint(mockComponent1, "key1", "value1");
+            assertThat(target.getHint(mockComponent1, "key1"), is("value1"));
+            target.clearHint(mockComponent1, "key1");
+            assertThat(target.getHint(mockComponent1, "key1"), is(nullValue()));
+        }
+
+        @Test
+        public void setToNull() throws Exception {
+            target.setHint(mockComponent1, "key1", "value1");
+            assertThat(target.getHint(mockComponent1, "key1"), is("value1"));
+            target.setHint(mockComponent1, "key1", null);
+            assertThat(target.getHint(mockComponent1, "key1"), is(nullValue()));
+        }
+
+        @Test
+        public void multipleKeys() throws Exception {
+            target.setHint(mockComponent1, "key1", "value1");
+            target.setHint(mockComponent1, "key2", "value2");
+            assertThat(target.getHint(mockComponent1, "key1"), is("value1"));
+            assertThat(target.getHint(mockComponent1, "key2"), is("value2"));
+        }
+
+        @Test
+        public void multipleComponents() throws Exception {
+            target.setHint(mockComponent1, "key", "valueA");
+            target.setHint(mockComponent2, "key", "valueB");
+            assertThat(target.getHint(mockComponent1, "key"), is("valueA"));
+            assertThat(target.getHint(mockComponent2, "key"), is("valueB"));
+        }
+
+        @Test
+        public void smoke() throws Exception {
+            target.setHint(mockComponent1, "X", "1.X");
+            target.setHint(mockComponent1, "A", "1.A");
+            target.setHint(mockComponent1, "B", "1.B");
+            target.setHint(mockComponent1, "C", "1.C");
+            target.setHint(mockComponent2, "X", "2.X");
+            target.setHint(mockComponent2, "P", "2.P");
+            target.setHint(mockComponent2, "Q", "2.Q");
+            target.setHint(mockComponent2, "R", "2.R");
+
+            final Map<String, String> hints = target.getHints();
+            assertThat(hints.size(), is(8));
+            assertThat(hints.get("id1-X"), is("1.X"));
+            assertThat(hints.get("id2-X"), is("2.X"));
+            assertThat(hints.get("id1-B"), is("1.B"));
+            assertThat(hints.get("id2-R"), is("2.R"));
+        }
+    }
+
+}
\ No newline at end of file


[7/7] isis git commit: ISIS-951: introducing menu separators; also getting rid of the btn-warning for prototype, using subtler italics instead.

Posted by da...@apache.org.
ISIS-951: introducing menu separators; also getting rid of the btn-warning for prototype, using subtler italics instead.


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/f436bcb1
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/f436bcb1
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/f436bcb1

Branch: refs/heads/master
Commit: f436bcb124830b3d94063ebdbb221c6ecb786d11
Parents: ad8e6b8
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Thu Nov 13 16:25:23 2014 +0000
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Thu Nov 13 16:25:23 2014 +0000

----------------------------------------------------------------------
 .../cssmenu/AppActionsCssMenuFactory.java       | 11 ++-
 .../cssmenu/ApplicationActionsPanel.html        |  6 +-
 .../cssmenu/ApplicationActionsPanel.java        | 81 ++++++++++++++------
 .../widgets/cssmenu/CssMenuBuilder.java         |  2 +-
 .../components/widgets/cssmenu/CssMenuItem.java | 38 ++++++---
 .../widgets/cssmenu/CssMenuItemPanel.html       |  2 +-
 .../widgets/cssmenu/CssMenuPanel.java           | 19 +----
 .../widgets/cssmenu/CssSubMenuItemsPanel.html   |  2 +-
 .../wicket/ui/pages/bootstrap-overrides.css     | 17 ++++
 .../exceprecog/ExceptionRecognizer.java         |  5 +-
 .../exceprecog/ExceptionRecognizer2.java        |  3 +
 .../ExceptionRecognizerComposite.java           |  6 ++
 12 files changed, 133 insertions(+), 59 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/f436bcb1/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/appactions/cssmenu/AppActionsCssMenuFactory.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/appactions/cssmenu/AppActionsCssMenuFactory.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/appactions/cssmenu/AppActionsCssMenuFactory.java
index 12d544c..3d9e8ba 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/appactions/cssmenu/AppActionsCssMenuFactory.java
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/appactions/cssmenu/AppActionsCssMenuFactory.java
@@ -61,7 +61,8 @@ public class AppActionsCssMenuFactory extends ComponentFactoryAbstract {
         private final ObjectAdapter serviceAdapter;
         private final ObjectAdapterMemento serviceAdapterMemento;
         private final ObjectAction objectAction;
-        
+        public boolean separator;
+
         LogicalServiceAction(final String serviceName, final ObjectAdapter serviceAdapter, final ObjectAction objectAction) {
             this.serviceName = serviceName;
             this.serviceAdapter = serviceAdapter;
@@ -133,7 +134,8 @@ public class AppActionsCssMenuFactory extends ComponentFactoryAbstract {
                 }
                 final ObjectAdapterMemento serviceAdapterMemento = logicalServiceAction.serviceAdapterMemento;
                 final ObjectAction objectAction = logicalServiceAction.objectAction;
-                final Builder subMenuItemBuilder = serviceMenuItem.newSubMenuItem(serviceAdapterMemento, objectAction, cssMenuContext);
+                final boolean separator = logicalServiceAction.separator;
+                final Builder subMenuItemBuilder = serviceMenuItem.newSubMenuItem(serviceAdapterMemento, objectAction, separator, cssMenuContext);
                 if (subMenuItemBuilder == null) {
                     // not visible
                     continue;
@@ -201,13 +203,18 @@ public class AppActionsCssMenuFactory extends ComponentFactoryAbstract {
         final Map<String, List<LogicalServiceAction>> serviceActionsByName = Maps.newTreeMap();
         
         // map available services
+        ObjectAdapter lastServiceAdapter = null;
         for (LogicalServiceAction serviceAction : serviceActions) {
             List<LogicalServiceAction> serviceActionsForName = serviceActionsByName.get(serviceAction.serviceName);
             if(serviceActionsForName == null) {
                 serviceActionsForName = Lists.newArrayList();
                 serviceActionsByName.put(serviceAction.serviceName, serviceActionsForName);
+            } else {
+                // capture whether this action is from a different service
+                serviceAction.separator = lastServiceAdapter != serviceAction.serviceAdapter;
             }
             serviceActionsForName.add(serviceAction);
+            lastServiceAdapter = serviceAction.serviceAdapter;
         }
         
         return serviceActionsByName;

http://git-wip-us.apache.org/repos/asf/isis/blob/f436bcb1/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/ApplicationActionsPanel.html
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/ApplicationActionsPanel.html b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/ApplicationActionsPanel.html
index 0a37c74..0562067 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/ApplicationActionsPanel.html
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/ApplicationActionsPanel.html
@@ -13,7 +13,7 @@
 
             <wicket:fragment wicket:id="leafItem">
                 <a class="menuLink" wicket:id="menuLink">
-                    <span class="fontAwesomeIcon" wicket:id="menuLinkFontAwesome"></span> <span wicket:id="menuLinkLabel"></span>
+                    <span class="fontAwesomeIcon" wicket:id="menuLinkFontAwesome"></span> <span class="menuLinkLabel" wicket:id="menuLinkLabel"></span>
                 </a>
             </wicket:fragment>
 
@@ -26,6 +26,10 @@
                 </ul>
             </wicket:fragment>
 
+            <wicket:fragment wicket:id="empty">
+            </wicket:fragment>
+
+
         </wicket:panel>
     </body>
 </html>

http://git-wip-us.apache.org/repos/asf/isis/blob/f436bcb1/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/ApplicationActionsPanel.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/ApplicationActionsPanel.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/ApplicationActionsPanel.java
index fa6f4be..48b34d4 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/ApplicationActionsPanel.java
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/ApplicationActionsPanel.java
@@ -6,6 +6,7 @@ import de.agilecoders.wicket.extensions.markup.html.bootstrap.button.DropdownAut
 
 import java.util.List;
 import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
 import org.apache.wicket.markup.head.CssHeaderItem;
 import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.markup.head.JavaScriptHeaderItem;
@@ -49,7 +50,7 @@ public class ApplicationActionsPanel extends Panel {
                 CssMenuItem menuItem = listItem.getModelObject();
                 listItem.add(new Label("name", menuItem.getName()));
 
-                List<CssMenuItem> subMenuItems = menuItem.getSubMenuItems();
+                List<CssMenuItem> subMenuItems = withSeparators(menuItem);
 
 // fake data to test multi-level menus
 
@@ -88,7 +89,9 @@ public class ApplicationActionsPanel extends Panel {
         listItem.add(folderItem);
 
         folderItem.add(new Label("folderName", subMenuItem.getName()));
-        ListView<CssMenuItem> subMenuItemsView = new ListView<CssMenuItem>("subMenuItems", subMenuItem.getSubMenuItems()) {
+        final List<CssMenuItem> menuItems = withSeparators(subMenuItem);
+        ListView<CssMenuItem> subMenuItemsView = new ListView<CssMenuItem>("subMenuItems",
+                menuItems) {
             @Override
             protected void populateItem(ListItem<CssMenuItem> listItem) {
                 CssMenuItem subMenuItem = listItem.getModelObject();
@@ -103,35 +106,63 @@ public class ApplicationActionsPanel extends Panel {
         folderItem.add(subMenuItemsView);
     }
 
-    private void addLeafItem(CssMenuItem menuItem, ListItem<CssMenuItem> listItem) {
-        Fragment leafItem = new Fragment("content", "leafItem", ApplicationActionsPanel.this);
+    private List<CssMenuItem> withSeparators(CssMenuItem subMenuItem) {
+        final List<CssMenuItem> subMenuItems = subMenuItem.getSubMenuItems();
+        final List<CssMenuItem> itemsWithSeparators = Lists.newArrayList();
+        for (CssMenuItem menuItem : subMenuItems) {
+            if(menuItem.isSeparator()) {
+                // nasty...
+                // ... we add it twice, but mutate it along the way
+                itemsWithSeparators.add(
+                        CssMenuItem.newMenuItem(menuItem.getName() + "-separator")
+                                .separator(menuItem.isSeparator())
+                                .prototyping(menuItem.isPrototyping())
+                                .build());
+                menuItem.setSeparator(false);
+            }
+            itemsWithSeparators.add(menuItem);
+        }
+        return itemsWithSeparators;
+    }
 
-        AbstractLink subMenuItemLink = menuItem.getLink();
+    private void addLeafItem(
+            final CssMenuItem menuItem,
+            final ListItem<CssMenuItem> listItem) {
 
-        Label menuItemLabel = new Label("menuLinkLabel", menuItem.getName());
-        subMenuItemLink.addOrReplace(menuItemLabel);
+        Fragment leafItem;
+        if (!menuItem.isSeparator()) {
+            leafItem = new Fragment("content", "leafItem", ApplicationActionsPanel.this);
 
-        if (!menuItem.isEnabled()) {
-            listItem.add(new CssClassNameAppender("disabled"));
-            subMenuItemLink.setEnabled(false);
-            TooltipBehavior tooltipBehavior = new TooltipBehavior(Model.of(menuItem.getDisabledReason()));
-            listItem.add(tooltipBehavior);
-        }
-        if (menuItem.isPrototyping()) {
-            subMenuItemLink.add(new CssClassNameAppender("btn btn-warning"));
-        }
-        leafItem.add(subMenuItemLink);
-        listItem.add(leafItem);
+            AbstractLink subMenuItemLink = menuItem.getLink();
 
-        String cssClassFa = menuItem.getCssClassFa();
-        if (Strings.isNullOrEmpty(cssClassFa)) {
-            Components.permanentlyHide(subMenuItemLink, "menuLinkFontAwesome");
-            subMenuItemLink.add(new CssClassAppender("menuLinkSpacer"));
+            Label menuItemLabel = new Label("menuLinkLabel", menuItem.getName());
+            subMenuItemLink.addOrReplace(menuItemLabel);
+
+            if (!menuItem.isEnabled()) {
+                listItem.add(new CssClassNameAppender("disabled"));
+                subMenuItemLink.setEnabled(false);
+                TooltipBehavior tooltipBehavior = new TooltipBehavior(Model.of(menuItem.getDisabledReason()));
+                listItem.add(tooltipBehavior);
+            }
+            if (menuItem.isPrototyping()) {
+                subMenuItemLink.add(new CssClassNameAppender("prototype"));
+            }
+            leafItem.add(subMenuItemLink);
+
+            String cssClassFa = menuItem.getCssClassFa();
+            if (Strings.isNullOrEmpty(cssClassFa)) {
+                Components.permanentlyHide(subMenuItemLink, "menuLinkFontAwesome");
+                subMenuItemLink.add(new CssClassAppender("menuLinkSpacer"));
+            } else {
+                Label dummy = new Label("menuLinkFontAwesome", "");
+                dummy.add(new CssClassAppender(cssClassFa));
+                subMenuItemLink.addOrReplace(dummy);
+            }
         } else {
-            Label dummy = new Label("menuLinkFontAwesome", "");
-            dummy.add(new CssClassAppender(cssClassFa));
-            subMenuItemLink.addOrReplace(dummy);
+            leafItem = new Fragment("content", "empty", ApplicationActionsPanel.this);
+            listItem.add(new CssClassNameAppender("divider"));
         }
+        listItem.add(leafItem);
 
     }
 

http://git-wip-us.apache.org/repos/asf/isis/blob/f436bcb1/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuBuilder.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuBuilder.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuBuilder.java
index 6b9189f..0b12d23 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuBuilder.java
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuBuilder.java
@@ -160,7 +160,7 @@ public class CssMenuBuilder {
         final ObjectAdapterMemento targetAdapterMemento = adapterMemento; // determineAdapterFor(action);
         if(targetAdapterMemento != null) {
             // against an entity or a service (if a contributed action)
-            subMenuItemBuilder = parent.newSubMenuItem(targetAdapterMemento, action, cssMenuContext);
+            subMenuItemBuilder = parent.newSubMenuItem(targetAdapterMemento, action, false, cssMenuContext);
         } else {
             if (action.containsDoOpFacet(BulkFacet.class)) {
                 // ignore fact have no target action; 

http://git-wip-us.apache.org/repos/asf/isis/blob/f436bcb1/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java
index bfbb539..05b0184 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java
@@ -104,20 +104,16 @@ public class CssMenuItem implements Serializable {
             return this;
         }
 
-        /**
-         * Access the {@link CssMenuItem} before it is attached to its parent.
-         * 
-         * @see #build()
-         */
-        public CssMenuItem itemBeingBuilt() {
-            return cssMenuItem;
-        }
-
         public Builder prototyping(boolean prototype) {
             cssMenuItem.setPrototyping(prototype);
             return this;
         }
 
+        public Builder separator(boolean separator) {
+            cssMenuItem.setSeparator(separator);
+            return this;
+        }
+
         public Builder withActionIdentifier(String actionIdentifier) {
             cssMenuItem.setActionIdentifier(actionIdentifier);
             return this;
@@ -169,6 +165,7 @@ public class CssMenuItem implements Serializable {
     private String disabledReason;
     private boolean blobOrClob = false; // unless set otherwise
     private boolean prototype = false; // unless set otherwise
+    private boolean separator = false; // unless set otherwise
 
     static final String ID_MENU_LABEL = "menuLabel";
 
@@ -202,6 +199,18 @@ public class CssMenuItem implements Serializable {
         return prototype;
     }
 
+    public void setSeparator(boolean separator) {
+        this.separator = separator;
+    }
+
+    /**
+     * Requires a separator before it
+     * @return
+     */
+    public boolean isSeparator() {
+        return separator;
+    }
+
     private CssMenuItem(final String name) {
         this.name = name;
     }
@@ -292,6 +301,7 @@ public class CssMenuItem implements Serializable {
     public Builder newSubMenuItem(
             final ObjectAdapterMemento targetAdapterMemento,
             final ObjectAction objectAction,
+            final boolean separator,
             final CssMenuBuilder.CssMenuContext cssMenuContext) {
 
         // check visibility
@@ -325,6 +335,7 @@ public class CssMenuItem implements Serializable {
                 .enabled(reasonDisabledIfAny)
                 .returnsBlobOrClob(returnsBlobOrClob(objectAction))
                 .prototyping(isExplorationOrPrototype(objectAction))
+                .separator(separator)
                 .withActionIdentifier(actionIdentifierFor(objectAction))
                 .withCssClass(cssClassFor(objectAction))
                 .withCssClassFa(cssClassFaFor(objectAction));
@@ -388,8 +399,8 @@ public class CssMenuItem implements Serializable {
         final String actionLabel = linkAndLabel.getLabel();
         Builder builder = this.newSubMenuItem(actionLabel)
                               .link(link)
-                              .prototyping(linkAndLabel.isPrototype())
-                              .returnsBlobOrClob(linkAndLabel.isBlobOrClob())
+                .prototyping(linkAndLabel.isPrototype())
+                .returnsBlobOrClob(linkAndLabel.isBlobOrClob())
                               .withFacet(objectAction.getFacet(CssClassFacet.class))
                               .withFacet(objectAction.getFacet(CssClassFaFacet.class));
         return builder;
@@ -423,10 +434,13 @@ public class CssMenuItem implements Serializable {
                 link.add(new CssClassAppender("noVeil"));
             }
             if(this.prototype) {
-                link.add(new CssClassAppender("btn-warning"));
+                link.add(new CssClassAppender("prototype"));
+                link.add(new CssClassAppender("btn-default"));
             } else {
                 link.add(new CssClassAppender("btn-default"));
             }
+
+
             if(this.cssClass != null) {
                 link.add(new CssClassAppender(this.cssClass));
             }

http://git-wip-us.apache.org/repos/asf/isis/blob/f436bcb1/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItemPanel.html
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItemPanel.html b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItemPanel.html
index 1d65257..7d80c8e 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItemPanel.html
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItemPanel.html
@@ -23,7 +23,7 @@
             <li class="cssMenuItemPanel">
                 <a wicket:id="menuLink" class="btn">
                     <span class="fontAwesomeIcon" wicket:id="menuLinkFontAwesome"></span>
-                    <span wicket:id="menuLabel">[menu label]</span>
+                    <span class="menuLabel" wicket:id="menuLabel">[menu label]</span>
                 </a>
 
                 <p wicket:id="menuLabel">[menu label]</p>

http://git-wip-us.apache.org/repos/asf/isis/blob/f436bcb1/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuPanel.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuPanel.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuPanel.java
index 9181e76..ad0c75a 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuPanel.java
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuPanel.java
@@ -19,7 +19,6 @@
 
 package org.apache.isis.viewer.wicket.ui.components.widgets.cssmenu;
 
-import java.util.Arrays;
 import java.util.List;
 import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.markup.html.WebMarkupContainer;
@@ -27,7 +26,6 @@ import org.apache.wicket.markup.repeater.RepeatingView;
 import org.apache.wicket.model.util.ListModel;
 import org.apache.isis.core.commons.lang.StringExtensions;
 import org.apache.isis.viewer.wicket.ui.ComponentFactory;
-import org.apache.isis.viewer.wicket.ui.ComponentType;
 import org.apache.isis.viewer.wicket.ui.panels.PanelAbstract;
 import org.apache.isis.viewer.wicket.ui.panels.PanelUtil;
 import org.apache.isis.viewer.wicket.ui.util.CssClassAppender;
@@ -39,7 +37,7 @@ import org.apache.isis.viewer.wicket.ui.util.CssClassAppender;
  * <p>
  * The {@link Style} enum allows the presentation to be altered.
  */
-public class CssMenuPanel extends PanelAbstract<CssMenuPanel.MyModel> {
+public class CssMenuPanel extends PanelAbstract<CssMenuPanel.ListOfCssMenuItemsModel> {
 
     private static final long serialVersionUID = 1L;
 
@@ -66,26 +64,21 @@ public class CssMenuPanel extends PanelAbstract<CssMenuPanel.MyModel> {
         }
     }
 
-    static class MyModel extends ListModel<CssMenuItem> {
+    static class ListOfCssMenuItemsModel extends ListModel<CssMenuItem> {
 
         private static final long serialVersionUID = 1L;
 
-        public MyModel(final List<CssMenuItem> cssMenuItems) {
+        public ListOfCssMenuItemsModel(final List<CssMenuItem> cssMenuItems) {
             super(cssMenuItems);
         }
     }
 
-    public static CssMenuItem.Builder newMenuItem(final String name) {
-        return CssMenuItem.newMenuItem(name);
-    }
-
     private final StyleAppender styleAppender;
     static final String ID_MENU_ITEMS = "menuItems";
-    static final String ID_MENU_ITEM_FONT_AWESOME = "menuItemFontAwesome";
     static final String ID_MENU_ITEM = "menuItem";
 
     public CssMenuPanel(final String id, final Style style, final List<CssMenuItem> topLevelMenuItems) {
-        super(id, new MyModel(topLevelMenuItems));
+        super(id, new ListOfCssMenuItemsModel(topLevelMenuItems));
         this.styleAppender = new StyleAppender(style);
 
         add(styleAppender);
@@ -102,10 +95,6 @@ public class CssMenuPanel extends PanelAbstract<CssMenuPanel.MyModel> {
 
     }
 
-    public CssMenuPanel(final ComponentType componentType, final Style style, final CssMenuItem... topLevelMenuItems) {
-        this(componentType.getWicketId(), style, Arrays.asList(topLevelMenuItems));
-    }
-
     static final class StyleAppender extends CssClassAppender {
 
         private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/f436bcb1/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssSubMenuItemsPanel.html
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssSubMenuItemsPanel.html b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssSubMenuItemsPanel.html
index f4e5fdf..4b4d00d 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssSubMenuItemsPanel.html
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssSubMenuItemsPanel.html
@@ -24,7 +24,7 @@
         <li wicket:id="subMenuItems" class="cssSubMenuItem">
             <a wicket:id="menuLink" class="btn">
                 <span class="fontAwesomeIcon" wicket:id="menuLinkFontAwesome"></span>
-                <span wicket:id="menuLabel">[menu label]</span>
+                <span class="menuLabel" wicket:id="menuLabel">[menu label]</span>
             </a>
             <p wicket:id="menuLabel">[menu label]</p>
             <span wicket:id="subMenuItems">[subMenuItems]</span>

http://git-wip-us.apache.org/repos/asf/isis/blob/f436bcb1/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/bootstrap-overrides.css
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/bootstrap-overrides.css b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/bootstrap-overrides.css
index 51c53d3..9e3e23d 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/bootstrap-overrides.css
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/bootstrap-overrides.css
@@ -172,3 +172,20 @@ div.label-left .choicesPlaceholder {
     width: 20px;
     height: 20px;
 }
+
+.prototype {
+    font-style: italic;
+}
+
+/*
+not sure if this also needed, but it works...
+a.prototype span.menuLabel::before,
+a.prototype span.menuLinkLabel::before {
+    content: "[";
+}
+
+a.prototype span.menuLabel::after,
+a.prototype span.menuLinkLabel::after {
+    content: "]";
+}
+*/
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/f436bcb1/core/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizer.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizer.java b/core/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizer.java
index 1b66626..258fa0e 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizer.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizer.java
@@ -22,7 +22,7 @@ import java.util.Map;
 
 import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
-
+import org.apache.isis.applib.annotation.Programmatic;
 
 
 /**
@@ -60,11 +60,14 @@ public interface ExceptionRecognizer {
      * 
      * @return user-friendly message to render, or <tt>null</tt> otherwise.
      */
+    @Programmatic
     public String recognize(Throwable ex);
 
+    @Programmatic
     @PostConstruct
     public void init(Map<String, String> properties);
 
+    @Programmatic
     @PreDestroy
     public void shutdown();
 

http://git-wip-us.apache.org/repos/asf/isis/blob/f436bcb1/core/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizer2.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizer2.java b/core/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizer2.java
index d69f9d3..89b2ae1 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizer2.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizer2.java
@@ -18,6 +18,8 @@
  */
 package org.apache.isis.applib.services.exceprecog;
 
+import org.apache.isis.applib.annotation.Programmatic;
+
 /**
  * An extension of the {@link org.apache.isis.applib.services.exceprecog.ExceptionRecognizer} interface that
  * allows recognized exceptions to be {@link org.apache.isis.applib.services.exceprecog.ExceptionRecognizer2.Category categorize}d.
@@ -77,6 +79,7 @@ public interface ExceptionRecognizer2 extends ExceptionRecognizer {
         }
     }
 
+    @Programmatic
     public Recognition recognize2(final Throwable ex);
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/f436bcb1/core/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizerComposite.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizerComposite.java b/core/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizerComposite.java
index 390e797..f3b6a63 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizerComposite.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizerComposite.java
@@ -28,6 +28,7 @@ import javax.annotation.PreDestroy;
 import com.google.common.collect.Lists;
 
 import org.apache.isis.applib.annotation.Hidden;
+import org.apache.isis.applib.annotation.Programmatic;
 
 /**
  * Convenience implementation of {@link ExceptionRecognizer} that loops through a list of
@@ -66,6 +67,7 @@ public class ExceptionRecognizerComposite implements ExceptionRecognizer2 {
      * before the more general ones.  See the <i>JDO object store</i> applib for
      * an example.
      */
+    @Programmatic
     public final void add(ExceptionRecognizer ers) {
         services.add(ers);
     }
@@ -74,6 +76,7 @@ public class ExceptionRecognizerComposite implements ExceptionRecognizer2 {
      * Returns the non-<tt>null</tt> message of the first {@link #add(ExceptionRecognizer) add}ed 
      * {@link ExceptionRecognizer service} that recognizes the exception. 
      */
+    @Programmatic
     public final  String recognize(Throwable ex) {
         for (ExceptionRecognizer ers : services) {
             String message = ers.recognize(ex);
@@ -95,6 +98,7 @@ public class ExceptionRecognizerComposite implements ExceptionRecognizer2 {
      *     category of {@link org.apache.isis.applib.services.exceprecog.ExceptionRecognizer2.Category#CLIENT_ERROR}.
      * </p>
      */
+    @Programmatic
     public final Recognition recognize2(Throwable ex) {
         for (ExceptionRecognizer ers : services) {
             if(ers instanceof ExceptionRecognizer2) {
@@ -111,6 +115,7 @@ public class ExceptionRecognizerComposite implements ExceptionRecognizer2 {
 
     @PostConstruct
     @Override
+    @Programmatic
     public final void init(Map<String, String> properties) {
         for (ExceptionRecognizer ers : services) {
             ers.init(properties);
@@ -119,6 +124,7 @@ public class ExceptionRecognizerComposite implements ExceptionRecognizer2 {
 
     @PreDestroy
     @Override
+    @Programmatic
     public final void shutdown() {
         for (ExceptionRecognizer ers : services) {
             ers.shutdown();