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:41 UTC

[50/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-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/IsisWicketApplication.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/IsisWicketApplication.java b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/IsisWicketApplication.java
new file mode 100644
index 0000000..ac1de1b
--- /dev/null
+++ b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/IsisWicketApplication.java
@@ -0,0 +1,806 @@
+/*
+ *  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.viewer;
+
+import de.agilecoders.wicket.core.Bootstrap;
+import de.agilecoders.wicket.core.markup.html.bootstrap.behavior.BootstrapBaseBehavior;
+import de.agilecoders.wicket.core.settings.BootstrapSettings;
+import de.agilecoders.wicket.core.settings.IBootstrapSettings;
+import de.agilecoders.wicket.webjars.WicketWebjars;
+import de.agilecoders.wicket.webjars.settings.IWebjarsSettings;
+import de.agilecoders.wicket.webjars.settings.WebjarsSettings;
+import net.ftlines.wicketsource.WicketSource;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.Set;
+import javax.servlet.ServletContext;
+import com.google.common.base.Function;
+import com.google.common.base.Throwables;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.vaynberg.wicket.select2.ApplicationSettings;
+import org.apache.wicket.Application;
+import org.apache.wicket.ConverterLocator;
+import org.apache.wicket.IConverterLocator;
+import org.apache.wicket.Page;
+import org.apache.wicket.RuntimeConfigurationType;
+import org.apache.wicket.SharedResources;
+import org.apache.wicket.authroles.authentication.AuthenticatedWebApplication;
+import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
+import org.apache.wicket.core.request.mapper.MountedMapper;
+import org.apache.wicket.guice.GuiceComponentInjector;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.filter.JavaScriptFilteredIntoFooterHeaderResponse;
+import org.apache.wicket.markup.html.IHeaderContributor;
+import org.apache.wicket.markup.html.IHeaderResponseDecorator;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.request.cycle.IRequestCycleListener;
+import org.apache.wicket.request.cycle.PageRequestHandlerTracker;
+import org.apache.wicket.request.cycle.RequestCycleListenerCollection;
+import org.apache.wicket.request.resource.CssResourceReference;
+import org.apache.wicket.settings.IRequestCycleSettings.RenderStrategy;
+import org.apache.wicket.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
+import org.apache.isis.core.commons.authentication.AuthenticationSessionProviderAware;
+import org.apache.isis.core.commons.config.IsisConfiguration;
+import org.apache.isis.core.commons.config.IsisConfigurationBuilder;
+import org.apache.isis.core.commons.config.IsisConfigurationBuilderPrimer;
+import org.apache.isis.core.commons.config.IsisConfigurationBuilderResourceStreams;
+import org.apache.isis.core.commons.resource.ResourceStreamSourceComposite;
+import org.apache.isis.core.commons.resource.ResourceStreamSourceContextLoaderClassPath;
+import org.apache.isis.core.commons.resource.ResourceStreamSourceCurrentClassClassPath;
+import org.apache.isis.core.commons.resource.ResourceStreamSourceFileSystem;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.specloader.validator.MetaModelInvalidException;
+import org.apache.isis.core.runtime.logging.IsisLoggingConfigurer;
+import org.apache.isis.core.runtime.runner.IsisInjectModule;
+import org.apache.isis.core.runtime.runner.opts.OptionHandlerInitParameters;
+import org.apache.isis.core.runtime.system.DeploymentType;
+import org.apache.isis.core.runtime.system.IsisSystem;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.core.webapp.IsisWebAppBootstrapperUtil;
+import org.apache.isis.core.webapp.WebAppConstants;
+import org.apache.isis.core.webapp.config.ResourceStreamSourceForWebInf;
+import org.apache.isis.viewer.wicket.model.isis.WicketViewerSettings;
+import org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento;
+import org.apache.isis.viewer.wicket.model.models.ImageResourceCache;
+import org.apache.isis.viewer.wicket.model.models.PageType;
+import org.apache.isis.viewer.wicket.ui.ComponentFactory;
+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.actionmenu.entityactions.AdditionalLinksPanel;
+import org.apache.isis.viewer.wicket.ui.components.scalars.string.MultiLineStringPanel;
+import org.apache.isis.viewer.wicket.ui.components.widgets.select2.Select2BootstrapCssReference;
+import org.apache.isis.viewer.wicket.ui.components.widgets.select2.Select2JsReference;
+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.pages.accmngt.AccountConfirmationMap;
+import org.apache.isis.viewer.wicket.ui.panels.PanelUtil;
+import org.apache.isis.viewer.wicket.viewer.integration.isis.DeploymentTypeWicketAbstract;
+import org.apache.isis.viewer.wicket.viewer.integration.isis.WicketServer;
+import org.apache.isis.viewer.wicket.viewer.integration.isis.WicketServerPrototype;
+import org.apache.isis.viewer.wicket.viewer.integration.wicket.AuthenticatedWebSessionForIsis;
+import org.apache.isis.viewer.wicket.viewer.integration.wicket.ConverterForObjectAdapter;
+import org.apache.isis.viewer.wicket.viewer.integration.wicket.ConverterForObjectAdapterMemento;
+import org.apache.isis.viewer.wicket.viewer.integration.wicket.WebRequestCycleForIsis;
+import org.apache.isis.viewer.wicket.viewer.settings.IsisResourceSettings;
+
+/**
+ * Main application, subclassing the Wicket {@link Application} and
+ * bootstrapping Isis.
+ *
+ * <p>
+ * Its main responsibility is to allow the set of {@link ComponentFactory}s used
+ * to render the domain objects to be registered. This type of customisation is
+ * common place. At a more fundamental level, also allows the {@link Page}
+ * implementation for each {@link PageType page type} to be overridden. This is
+ * probably less common, because CSS can also be used for this purpose.
+ *
+ * <p>
+ * New {@link ComponentFactory}s can be specified in two ways. The preferred
+ * approach is to use the {@link ServiceLoader} mechanism, whereby the
+ * {@link ComponentFactory} implementation class is specified in a file under
+ * <tt>META-INF/services</tt>. See <tt>views-gmaps2</tt> for an example of this.
+ * Including a jar that uses this mechanism on the classpath will automatically
+ * make the {@link ComponentFactory} defined within it available.
+ *
+ * <p>
+ * Alternatively, {@link ComponentFactory}s can be specified by overridding {@link #newIsisWicketModule()}.
+ * This mechanism allows a number of other aspects to be customized.
+ */
+public class IsisWicketApplication extends AuthenticatedWebApplication implements ComponentFactoryRegistryAccessor, PageClassRegistryAccessor, AuthenticationSessionProvider {
+
+    private static final long serialVersionUID = 1L;
+    
+    private static final Logger LOG = LoggerFactory.getLogger(IsisWicketApplication.class);
+
+    private static final String STRIP_WICKET_TAGS_KEY = "isis.viewer.wicket.stripWicketTags";
+    private static final boolean STRIP_WICKET_TAGS_DEFAULT = true;
+    private static final String AJAX_DEBUG_MODE_KEY = "isis.viewer.wicket.ajaxDebugMode";
+    private static final boolean AJAX_DEBUG_MODE_DEFAULT = false;
+
+    private final IsisLoggingConfigurer loggingConfigurer = new IsisLoggingConfigurer();
+
+    /**
+     * Convenience locator, downcasts inherited functionality.
+     */
+    public static IsisWicketApplication get() {
+        return (IsisWicketApplication) AuthenticatedWebApplication.get();
+    }
+
+    /**
+     * {@link Inject}ed when {@link #init() initialized}.
+     */
+    @Inject
+    private ComponentFactoryRegistry componentFactoryRegistry;
+
+    /**
+     * {@link Inject}ed when {@link #init() initialized}.
+     */
+    @SuppressWarnings("unused")
+    @Inject
+    private ImageResourceCache imageCache;
+
+    /**
+     * {@link Inject}ed when {@link #init() initialized}.
+     */
+    @SuppressWarnings("unused")
+    @Inject
+    private WicketViewerSettings wicketViewerSettings;
+
+    /**
+     * {@link Inject}ed when {@link #init() initialized}.
+     */
+    @Inject
+    private PageClassRegistry pageClassRegistry;
+
+
+    /**
+     * {@link Inject}ed when {@link #init() initialized}.
+     */
+    @SuppressWarnings("unused")
+    @Inject
+    private IsisSystem system;
+
+
+    private boolean determiningDeploymentType;
+    private DeploymentTypeWicketAbstract deploymentType;
+
+    private final List<String> validationErrors = Lists.newArrayList();
+
+
+    // /////////////////////////////////////////////////
+    // constructor, init
+    // /////////////////////////////////////////////////
+
+    public IsisWicketApplication() {
+    }
+
+
+    /**
+     * Although there are warnings about not overriding this method, it doesn't seem possible
+     * to call {@link #setResourceSettings(org.apache.wicket.settings.IResourceSettings)} in the
+     * {@link #init()} method.
+     */
+    @Override
+    protected void internalInit() {
+        // replace with custom implementation of ResourceSettings that changes the order
+        // in which search for i18n properties, to search for the application-specific
+        // settings before any other.
+        setResourceSettings(new IsisResourceSettings(this));
+
+        super.internalInit();
+    }
+
+    /**
+     * Initializes the application; in particular, bootstrapping the Isis
+     * backend, and initializing the {@link ComponentFactoryRegistry} to be used
+     * for rendering.
+     */
+    @Override
+    protected void init() {
+        try {
+            super.init();
+
+            configureWebJars();
+            configureWicketBootstrap();
+            configureWicketSelect2();
+
+            String isisConfigDir = getServletContext().getInitParameter("isis.config.dir");
+
+            configureLogging(isisConfigDir);
+    
+            getRequestCycleSettings().setRenderStrategy(RenderStrategy.REDIRECT_TO_RENDER);
+
+            getResourceSettings().setParentFolderPlaceholder("$up$");
+
+            determineDeploymentTypeIfRequired();
+
+            RequestCycleListenerCollection requestCycleListeners = getRequestCycleListeners();
+            IRequestCycleListener requestCycleListenerForIsis = newWebRequestCycleForIsis();
+            requestCycleListeners.add(requestCycleListenerForIsis);
+            requestCycleListeners.add(new PageRequestHandlerTracker());
+
+            final IsisConfigurationBuilder isisConfigurationBuilder = createConfigBuilder();
+    
+            final IsisInjectModule isisModule = newIsisModule(deploymentType, isisConfigurationBuilder);
+            final Injector injector = Guice.createInjector(isisModule, newIsisWicketModule());
+            initWicketComponentInjection(injector);
+            injector.injectMembers(this);
+
+            if (requestCycleListenerForIsis instanceof WebRequestCycleForIsis) {
+                WebRequestCycleForIsis webRequestCycleForIsis = (WebRequestCycleForIsis) requestCycleListenerForIsis;
+                webRequestCycleForIsis.setPageClassRegistry(pageClassRegistry);
+            }
+            
+            final IsisConfiguration configuration = isisConfigurationBuilder.getConfiguration();
+            this.getMarkupSettings().setStripWicketTags(determineStripWicketTags(configuration));
+
+            getDebugSettings().setAjaxDebugModeEnabled(determineAjaxDebugModeEnabled(configuration));
+
+            // must be done after injected componentFactoryRegistry into the app itself
+            buildCssBundle();
+
+            filterJavascriptContributions();
+
+            configureWicketSourcePlugin();
+
+            // TODO ISIS-987 Either make the API better (no direct access to the map) or use DB records
+            int maxEntries = 1000;
+            setMetaData(AccountConfirmationMap.KEY, new AccountConfirmationMap(maxEntries, Duration.days(1)));
+
+            mountPages();
+
+            @SuppressWarnings("unused")
+            SharedResources sharedResources = getSharedResources();
+            
+        } catch(RuntimeException ex) {
+            List<MetaModelInvalidException> mmies = locateMetaModelInvalidExceptions(ex);
+            if(!mmies.isEmpty()) {
+                final MetaModelInvalidException mmie = mmies.get(0);
+                log("");
+                logBanner();
+                log("");
+                validationErrors.addAll(mmie.getValidationErrors());
+                for (String validationError : validationErrors) {
+                    logError(validationError);
+                }
+                log("");
+                log("Please inspect the above messages and correct your domain model.");
+                log("");
+                logBanner();
+                log("");
+            } else {
+                // because Wicket's handling in its WicketFilter (that calls this method) does not log the exception.
+                LOG.error("Failed to initialize", ex);
+                throw ex;
+            }
+        }
+    }
+
+    private void configureWicketSelect2() {
+        ApplicationSettings select2Settings = ApplicationSettings.get();
+        select2Settings.setCssReference(new Select2BootstrapCssReference());
+        select2Settings.setJavaScriptReference(new Select2JsReference());
+        select2Settings.setIncludeJqueryUI(false);
+    }
+
+    private void configureWicketSourcePlugin() {
+        if(!deploymentType.isProduction()) {
+            WicketSource.configure(this);
+        }
+    }
+
+    // //////////////////////////////////////
+
+    /**
+     * Install 2 default collector instances: (FileAssetPathCollector(WEBJARS_PATH_PREFIX), JarAssetPathCollector),
+     * and a webjars resource finder.
+     *
+     * <p>
+     * Factored out for easy (informal) pluggability.
+     * </p>
+     */
+    protected void configureWebJars() {
+        IWebjarsSettings settings = new WebjarsSettings();
+        WicketWebjars.install(this, settings);
+    }
+
+    protected void configureWicketBootstrap() {
+        final IBootstrapSettings settings = new BootstrapSettings();
+        Bootstrap.install(this, settings);
+
+        getHeaderContributorListenerCollection().add(new IHeaderContributor() {
+            @Override
+            public void renderHead(IHeaderResponse response) {
+                BootstrapBaseBehavior bootstrapBaseBehavior = new BootstrapBaseBehavior();
+                bootstrapBaseBehavior.renderHead(settings, response);
+            }
+        });
+    }
+
+    // //////////////////////////////////////
+
+    /**
+     * Factored out for easy (informal) pluggability.
+     */
+    protected void configureLogging(String isisConfigDir) {
+        final String loggingPropertiesDir;
+        if(isisConfigDir != null) {
+            loggingPropertiesDir = isisConfigDir;
+        } else {
+            loggingPropertiesDir = getServletContext().getRealPath("/WEB-INF");
+        }
+
+        loggingConfigurer.configureLogging(loggingPropertiesDir, new String[0]);
+    }
+
+    // //////////////////////////////////////
+
+    /**
+     * Factored out for easy (informal) pluggability.
+     */
+    protected IRequestCycleListener newWebRequestCycleForIsis() {
+        return new WebRequestCycleForIsis();
+    }
+
+    // //////////////////////////////////////
+
+    /**
+     * Made protected visibility for easy (informal) pluggability.
+     */
+    protected void determineDeploymentTypeIfRequired() {
+        if(deploymentType != null) {
+            return;
+        }
+
+        determiningDeploymentType = true;
+        try {
+            final IsisConfigurationBuilder isisConfigurationBuilder = createConfigBuilder();
+            final IsisConfiguration configuration = isisConfigurationBuilder.getConfiguration();
+            String deploymentTypeFromConfig = configuration.getString("isis.deploymentType");
+            deploymentType = determineDeploymentType(deploymentTypeFromConfig);
+        } finally {
+            determiningDeploymentType = false;
+        }
+    }
+
+    /**
+     * Made protected visibility for easy (informal) pluggability.
+     */
+    protected DeploymentTypeWicketAbstract determineDeploymentType(String deploymentTypeFromConfig) {
+        final DeploymentTypeWicketAbstract prototype = new WicketServerPrototype();
+        final DeploymentTypeWicketAbstract deployment = new WicketServer();
+
+        if(deploymentTypeFromConfig != null) {
+            final DeploymentType deploymentType = DeploymentType.lookup(deploymentTypeFromConfig);
+            return !deploymentType.getDeploymentCategory().isProduction()
+                    ? prototype
+                    : deployment;
+        } else {
+            return usesDevelopmentConfig()
+                    ? prototype
+                    : deployment;
+        }
+    }
+
+
+    // //////////////////////////////////////
+
+    private IsisConfigurationBuilder createConfigBuilder() {
+        return createConfigBuilder(getServletContext());
+    }
+
+    protected IsisConfigurationBuilder createConfigBuilder(final ServletContext servletContext) {
+
+        final String configLocation = servletContext.getInitParameter(WebAppConstants.CONFIG_DIR_PARAM);
+        final ResourceStreamSourceForWebInf rssWebInf = new ResourceStreamSourceForWebInf(servletContext);
+        final ResourceStreamSourceContextLoaderClassPath rssContextLoaderClassPath = ResourceStreamSourceContextLoaderClassPath.create();
+        final ResourceStreamSourceCurrentClassClassPath rssCurrentClassPath = new ResourceStreamSourceCurrentClassClassPath();
+        final ResourceStreamSourceComposite compositeSource = new ResourceStreamSourceComposite(rssWebInf, rssContextLoaderClassPath, rssCurrentClassPath);
+
+        if ( configLocation != null ) {
+            LOG.info( "Config override location: " + configLocation );
+            compositeSource.addResourceStreamSource(ResourceStreamSourceFileSystem.create(configLocation));
+        } else {
+            LOG.info( "Config override location: No override location configured!" );
+        }
+
+        final IsisConfigurationBuilder configurationBuilder = new IsisConfigurationBuilderResourceStreams(compositeSource);
+
+        primeConfigurationBuilder(configurationBuilder, servletContext);
+        configurationBuilder.addDefaultConfigurationResources();
+
+        IsisWebAppBootstrapperUtil.addConfigurationResourcesForViewers(configurationBuilder, servletContext);
+        return configurationBuilder;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static void primeConfigurationBuilder(final IsisConfigurationBuilder isisConfigurationBuilder, final ServletContext servletContext) {
+        LOG.info("loading properties from option handlers");
+        final List<IsisConfigurationBuilderPrimer> isisConfigurationBuilderPrimers = Lists.newArrayList();
+        final List<IsisConfigurationBuilderPrimer> primers = (List<IsisConfigurationBuilderPrimer>) servletContext.getAttribute(WebAppConstants.CONFIGURATION_PRIMERS_KEY);
+        if(primers != null) {
+            isisConfigurationBuilderPrimers.addAll(primers);
+        }
+        // also support loading from init parameters (specifically, to support simplericity's jetty-console)
+        isisConfigurationBuilderPrimers.add(new OptionHandlerInitParameters(servletContext));
+        for (final IsisConfigurationBuilderPrimer isisConfigurationBuilderPrimer : isisConfigurationBuilderPrimers) {
+            LOG.info("priming configurations for " + isisConfigurationBuilderPrimer);
+            isisConfigurationBuilderPrimer.primeConfigurationBuilder(isisConfigurationBuilder);
+        }
+    }
+
+    // //////////////////////////////////////
+
+    /**
+     * Override if required
+     */
+    protected Module newIsisWicketModule() {
+        return new IsisWicketModule();
+    }
+
+    // //////////////////////////////////////
+
+    /**
+     * Made protected visibility for easy (informal) pluggability.
+     */
+    protected void buildCssBundle() {
+        // get the css for all components built by component factories
+        final Set<CssResourceReference> references = cssResourceReferencesForAllComponents();
+
+        // some additional special cases.
+        addSpecialCasesToCssBundle(references);
+
+        // create the bundle
+        getResourceBundles().addCssBundle(
+                IsisWicketApplication.class, "isis-wicket-viewer-bundle.css",
+                references.toArray(new CssResourceReference[]{}));
+    }
+
+    /**
+     * Additional special cases to be included in the main CSS bundle.
+     *
+     * <p>
+     * These are typically either superclasses or components that don't have their own ComponentFactory, or
+     * for {@link ComponentFactory}s (such as <tt>StringPanelFactory</tt>) that don't quite follow the usual pattern
+     * (because they create different types of panels).
+     *
+     * <p>
+     * Note that it doesn't really matter if we miss one or two; their CSS will simply be served up individually.
+     */
+    protected void addSpecialCasesToCssBundle(final Set<CssResourceReference> references) {
+
+        // abstract classes
+
+        // ... though it turns out we cannot add this particular one to the bundle, because
+        // it has CSS image links intended to be resolved relative to LinksSelectorPanelAbstract.class.
+        // Adding them into the bundle would mean these CSS links are resolved relative to IsisWicketApplication.class
+        // instead.
+        // references.add(PanelUtil.cssResourceReferenceFor(LinksSelectorPanelAbstract.class));
+
+        // components without factories
+        references.add(PanelUtil.cssResourceReferenceFor(AdditionalLinksPanel.class));
+
+        // non-conforming component factories
+        references.add(PanelUtil.cssResourceReferenceFor(MultiLineStringPanel.class));
+    }
+
+    protected final static Function<ComponentFactory, Iterable<CssResourceReference>> getCssResourceReferences =
+            new Function<ComponentFactory, Iterable<CssResourceReference>>(){
+                @Override
+                public Iterable<CssResourceReference> apply(final ComponentFactory input) {
+                    final CssResourceReference cssResourceReference = input.getCssResourceReference();
+                    return cssResourceReference != null?
+                            Collections.singletonList(cssResourceReference):
+                            Collections.<CssResourceReference>emptyList();
+                }
+            };
+
+
+    protected Set<CssResourceReference> cssResourceReferencesForAllComponents() {
+        // TODO mgrigorov: ISIS-537 temporary disabled to not mess up with Bootstrap styles
+//        Collection<ComponentFactory> componentFactories = getComponentFactoryRegistry().listComponentFactories();
+        return Sets.newLinkedHashSet(
+//                Iterables.concat(
+//                        Iterables.transform(
+//                                componentFactories,
+//                                getCssResourceReferences))
+        );
+    }
+
+    // //////////////////////////////////////
+
+    /**
+     * filters Javascript header contributions so rendered to bottom of page.
+     *
+     * <p>
+     * Factored out for easy (informal) pluggability.
+     * </p>
+     */
+    protected void filterJavascriptContributions() {
+        setHeaderResponseDecorator(new IHeaderResponseDecorator()
+        {
+            @Override
+            public IHeaderResponse decorate(IHeaderResponse response)
+            {
+                // use this header resource decorator to load all JavaScript resources in the page
+                // footer (after </body>)
+                return new JavaScriptFilteredIntoFooterHeaderResponse(response, "footerJS");
+            }
+        });
+    }
+
+    // //////////////////////////////////////
+
+    /**
+     * Map entity and action to provide prettier URLs.
+     *
+     * <p>
+     * Factored out for easy (informal) pluggability.
+     * </p>
+     */
+    protected void mountPages() {
+
+        mountPage("/signin", PageType.SIGN_IN);
+        mountPage("/signup", PageType.SIGN_UP);
+        mountPage("/signup/verify", PageType.SIGN_UP_VERIFY);
+        mountPage("/password/reset", PageType.PASSWORD_RESET);
+
+        mountPage("/entity/#{objectOid}", PageType.ENTITY);
+
+        // nb: action mount cannot contain {actionArgs}, because the default
+        // parameters encoder doesn't seem to be able to handle multiple args
+        mountPage("/action/${objectOid}/${actionOwningSpec}/${actionId}/${actionType}", PageType.ACTION_PROMPT);
+    }
+
+    protected void mountPage(final String mountPath, final PageType pageType) {
+        final Class<? extends Page> pageClass = this.pageClassRegistry.getPageClass(pageType);
+        mount(new MountedMapper(mountPath, pageClass));
+    }
+
+
+    // //////////////////////////////////////
+
+    protected List<MetaModelInvalidException> locateMetaModelInvalidExceptions(RuntimeException ex) {
+        return Lists.newArrayList(
+                Iterables.filter(Throwables.getCausalChain(ex), MetaModelInvalidException.class));
+    }
+
+    /**
+     * The validation errors, if any, that occurred on {@link #init() startup}.
+     */
+    public List<String> getValidationErrors() {
+        return validationErrors;
+    }
+    
+    private void logError(String validationError) {
+        log(validationError);
+    }
+
+    private static void logBanner() {
+        String msg = "################################################ ISIS METAMODEL VALIDATION ERRORS ################################################################";
+        log(msg);
+    }
+
+    private static void log(String msg) {
+        System.err.println(msg);
+        LOG.error(msg);
+    }
+
+    // //////////////////////////////////////
+
+    /**
+     * Whether Wicket tags should be stripped from the markup, as specified by configuration settings..
+     * 
+     * <p>
+     * If the <tt>isis.viewer.wicket.stripWicketTags</tt> is set, then this is used, otherwise the default is to strip 
+     * the tags because they may break some CSS rules.
+     */
+    private boolean determineStripWicketTags(IsisConfiguration configuration) {
+        final boolean strip = configuration.getBoolean(STRIP_WICKET_TAGS_KEY, STRIP_WICKET_TAGS_DEFAULT);
+        return strip;
+    }
+
+    // //////////////////////////////////////
+
+    /**
+     * Whether the Ajax debug should be shown, as specified by configuration settings.
+     *
+     * <p>
+     * If the <tt>isis.viewer.wicket.ajaxDebugMode</tt> is set, then this is used, otherwise the default is to disable.
+     */
+    private boolean determineAjaxDebugModeEnabled(IsisConfiguration configuration) {
+        final boolean debugMode = configuration.getBoolean(AJAX_DEBUG_MODE_KEY, AJAX_DEBUG_MODE_DEFAULT);
+        return debugMode;
+    }
+
+    // //////////////////////////////////////
+
+    @Override
+    protected void onDestroy() {
+        try {
+            if (system != null) {
+                system.shutdown();
+            }
+            IsisContext.shutdown();
+            super.onDestroy();
+        } catch(final RuntimeException ex) {
+            // symmetry with #init()
+            LOG.error("Failed to destroy", ex);
+            throw ex;
+        }
+    }
+
+    // //////////////////////////////////////
+
+    @Override
+    public RuntimeConfigurationType getConfigurationType() {
+        if(determiningDeploymentType) {
+            // avoiding an infinite loop; have already passed through here once before
+            // this time around, just delegate to web-inf
+            return super.getConfigurationType();
+        }
+        determineDeploymentTypeIfRequired();
+        return deploymentType.getConfigurationType();
+    }
+    
+    protected IsisInjectModule newIsisModule(final DeploymentType deploymentType, final IsisConfigurationBuilder isisConfigurationBuilder) {
+        return new IsisInjectModule(deploymentType, isisConfigurationBuilder);
+    }
+
+    // //////////////////////////////////////
+
+
+    protected void initWicketComponentInjection(final Injector injector) {
+        // if serializable, then brings in dependency on cglib, and in turn asm.
+        // This would block us from migrating to DN 4.0.x
+        getComponentInstantiationListeners().add(new GuiceComponentInjector(this, injector, false));
+    }
+
+    // /////////////////////////////////////////////////
+    // Wicket Hooks
+    // /////////////////////////////////////////////////
+
+    /**
+     * Installs a {@link AuthenticatedWebSessionForIsis custom implementation}
+     * of Wicket's own {@link AuthenticatedWebSession}, effectively associating
+     * the Wicket session with the Isis's equivalent session object.
+     *
+     * <p>
+     * In general, it shouldn't be necessary to override this method.
+     */
+    @Override
+    protected Class<? extends AuthenticatedWebSession> getWebSessionClass() {
+        return AuthenticatedWebSessionForIsis.class;
+    }
+
+    /**
+     * Installs a {@link ConverterLocator} preconfigured with a number of
+     * implementations to support Isis specific objects.
+     */
+    @Override
+    protected IConverterLocator newConverterLocator() {
+        final ConverterLocator converterLocator = new ConverterLocator();
+        converterLocator.set(ObjectAdapter.class, new ConverterForObjectAdapter());
+        converterLocator.set(ObjectAdapterMemento.class, new ConverterForObjectAdapterMemento());
+        return converterLocator;
+    }
+
+    // /////////////////////////////////////////////////
+    // Component Factories
+    // /////////////////////////////////////////////////
+
+    @Override
+    public final ComponentFactoryRegistry getComponentFactoryRegistry() {
+        return componentFactoryRegistry;
+    }
+
+    // /////////////////////////////////////////////////
+    // Page Registry
+    // /////////////////////////////////////////////////
+
+    /**
+     * Access to other page types.
+     *
+     * <p>
+     * Non-final only for testing purposes; should not typically be overridden.
+     */
+    @Override
+    public PageClassRegistry getPageClassRegistry() {
+        return pageClassRegistry;
+    }
+
+    
+    /**
+     * Delegates to the {@link #getPageClassRegistry() PageClassRegistry}.
+     */
+    @Override
+    public Class<? extends Page> getHomePage() {
+        return getPageClassRegistry().getPageClass(PageType.HOME);
+    }
+
+
+    /**
+     * Delegates to the {@link #getPageClassRegistry() PageClassRegistry}.
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public Class<? extends WebPage> getSignInPageClass() {
+        return (Class<? extends WebPage>) getPageClassRegistry().getPageClass(PageType.SIGN_IN);
+    }
+
+    /**
+     * Delegates to the {@link #getPageClassRegistry() PageClassRegistry}.
+     */
+    @SuppressWarnings("unchecked")
+    public Class<? extends WebPage> getSignUpPageClass() {
+        return (Class<? extends WebPage>) getPageClassRegistry().getPageClass(PageType.SIGN_UP);
+    }
+
+    /**
+     * Delegates to the {@link #getPageClassRegistry() PageClassRegistry}.
+     */
+    @SuppressWarnings("unchecked")
+    public Class<? extends WebPage> getSignUpVerifyPageClass() {
+        return (Class<? extends WebPage>) getPageClassRegistry().getPageClass(PageType.SIGN_UP_VERIFY);
+    }
+
+    /**
+     * Delegates to the {@link #getPageClassRegistry() PageClassRegistry}.
+     */
+    @SuppressWarnings("unchecked")
+    public Class<? extends WebPage> getForgotPasswordPageClass() {
+        return (Class<? extends WebPage>) getPageClassRegistry().getPageClass(PageType.PASSWORD_RESET);
+    }
+
+
+    // /////////////////////////////////////////////////
+    // Authentication Session
+    // /////////////////////////////////////////////////
+
+    @Override
+    public AuthenticationSession getAuthenticationSession() {
+        return IsisContext.getAuthenticationSession();
+    }
+
+
+    // /////////////////////////////////////////////////
+    // *Provider impl.
+    // /////////////////////////////////////////////////
+
+    @Override
+    public void injectInto(final Object candidate) {
+        if (AuthenticationSessionProviderAware.class.isAssignableFrom(candidate.getClass())) {
+            final AuthenticationSessionProviderAware cast = AuthenticationSessionProviderAware.class.cast(candidate);
+            cast.setAuthenticationSessionProvider(this);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/IsisWicketModule.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/IsisWicketModule.java b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/IsisWicketModule.java
new file mode 100644
index 0000000..219c379
--- /dev/null
+++ b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/IsisWicketModule.java
@@ -0,0 +1,102 @@
+/*
+ *  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.viewer;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.name.Names;
+import com.google.inject.util.Providers;
+
+import org.apache.isis.applib.AppManifest;
+import org.apache.isis.applib.services.email.EmailService;
+import org.apache.isis.applib.services.userreg.EmailNotificationService;
+import org.apache.isis.core.runtime.services.email.EmailServiceDefault;
+import org.apache.isis.core.runtime.services.userreg.EmailNotificationServiceDefault;
+import org.apache.isis.core.runtime.systemusinginstallers.IsisSystemThatUsesInstallersFactory;
+import org.apache.isis.viewer.wicket.model.isis.WicketViewerSettings;
+import org.apache.isis.viewer.wicket.model.models.ImageResourceCache;
+import org.apache.isis.viewer.wicket.ui.app.registry.ComponentFactoryRegistrar;
+import org.apache.isis.viewer.wicket.ui.app.registry.ComponentFactoryRegistry;
+import org.apache.isis.viewer.wicket.ui.pages.EmailVerificationUrlService;
+import org.apache.isis.viewer.wicket.ui.pages.PageClassList;
+import org.apache.isis.viewer.wicket.ui.pages.PageClassRegistry;
+import org.apache.isis.viewer.wicket.ui.pages.PageNavigationService;
+import org.apache.isis.viewer.wicket.ui.pages.accmngt.EmailVerificationUrlServiceDefault;
+import org.apache.isis.viewer.wicket.viewer.imagecache.ImageResourceCacheClassPath;
+import org.apache.isis.viewer.wicket.viewer.registries.components.ComponentFactoryRegistrarDefault;
+import org.apache.isis.viewer.wicket.viewer.registries.components.ComponentFactoryRegistryDefault;
+import org.apache.isis.viewer.wicket.viewer.registries.pages.PageClassListDefault;
+import org.apache.isis.viewer.wicket.viewer.registries.pages.PageClassRegistryDefault;
+import org.apache.isis.viewer.wicket.viewer.registries.pages.PageNavigationServiceDefault;
+import org.apache.isis.viewer.wicket.viewer.settings.WicketViewerSettingsDefault;
+
+/**
+ * To override
+ * 
+ * <pre>
+ * final Module isisDefaults = new IsisWicketModule();
+ *
+ * final Module myAppOverrides = new AbstractModule() {
+ *     @Override
+ *     protected void configure() {
+ *         bind(ComponentFactoryRegistrar.class).to(ComponentFactoryRegistrarForMyApp.class);
+ *         bind(PageClassList.class).to(PageClassListForMyApp.class);
+ *         ...  
+ *         bind(String.class).annotatedWith(Names.named("applicationName")).toInstance("My App");
+ *         bind(String.class).annotatedWith(Names.named("brandLogoHeader")).toInstance("/images/myapp-logo-header.png");
+ *         bind(String.class).annotatedWith(Names.named("brandLogoSignin")).toInstance("/images/myapp-logo-signin.png");
+ *         bind(String.class).annotatedWith(Names.named("applicationCss")).toInstance("css/application.css");
+ *         bind(String.class).annotatedWith(Names.named("applicationJs")).toInstance("scripts/application.js");
+ *         bind(String.class).annotatedWith(Names.named("welcomeMessage")).toInstance("Hello, welcome to my app");
+ *         bind(String.class).annotatedWith(Names.named("aboutMessage")).toInstance("MyApp v1.0.0");
+ *         bind(AppManifest.class).toInstance(new MyAppManifest());
+ *      }
+ *  };
+ * final Module overridden = Modules.override(isisDefaults).with(myAppOverrides);
+ * </pre>
+ */
+public class IsisWicketModule extends AbstractModule {
+
+    @Override
+    protected void configure() {
+        bind(ComponentFactoryRegistry.class).to(ComponentFactoryRegistryDefault.class);
+        bind(PageClassRegistry.class).to(PageClassRegistryDefault.class);
+        bind(EmailVerificationUrlService.class).to(EmailVerificationUrlServiceDefault.class);
+        bind(PageNavigationService.class).to(PageNavigationServiceDefault.class);
+        bind(PageClassList.class).to(PageClassListDefault.class);
+        bind(ComponentFactoryRegistrar.class).to(ComponentFactoryRegistrarDefault.class);
+        bind(ImageResourceCache.class).to(ImageResourceCacheClassPath.class);
+        bind(WicketViewerSettings.class).to(WicketViewerSettingsDefault.class);
+
+        // these services need to be bound because they injected directly into
+        // Wicket panels outside of the Isis runtime.
+        bind(EmailService.class)
+                .to(EmailServiceDefault.class);
+        bind(EmailNotificationService.class)
+                .to(EmailNotificationServiceDefault.class);
+
+        bind(String.class).annotatedWith(Names.named("applicationName")).toInstance("Apache Isis Wicket Viewer");
+        bind(String.class).annotatedWith(Names.named("applicationCss")).toProvider(Providers.of((String) null));
+        bind(String.class).annotatedWith(Names.named("applicationJs")).toProvider(Providers.of((String)null));
+        bind(String.class).annotatedWith(Names.named("welcomeMessage")).toProvider(Providers.of((String)null));
+        bind(String.class).annotatedWith(Names.named("aboutMessage")).toProvider(Providers.of((String)null));
+
+        bind(AppManifest.class).toInstance(IsisSystemThatUsesInstallersFactory.NOOP);
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/imagecache/ImageResourceCacheClassPath.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/imagecache/ImageResourceCacheClassPath.java b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/imagecache/ImageResourceCacheClassPath.java
new file mode 100644
index 0000000..45b9948
--- /dev/null
+++ b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/imagecache/ImageResourceCacheClassPath.java
@@ -0,0 +1,110 @@
+/*
+ *  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.viewer.imagecache;
+
+import images.Images;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import com.google.common.collect.Maps;
+import com.google.inject.Singleton;
+import org.apache.wicket.request.resource.PackageResourceReference;
+import org.apache.wicket.request.resource.ResourceReference;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.viewer.wicket.model.models.ImageResourceCache;
+
+/**
+ * Caches images loaded up the <tt>images</tt> package (using the {@link Images}
+ * class).
+ * 
+ * <p>
+ * Searches for a fixed set of suffixes: {@link #IMAGE_SUFFICES}.
+ */
+@Singleton
+public class ImageResourceCacheClassPath implements ImageResourceCache {
+
+    private static final long serialVersionUID = 1L;
+    
+    private static final List<String> IMAGE_SUFFICES = Arrays.asList("png", "gif", "jpeg", "jpg");
+    private static final String FALLBACK_IMAGE = "Default.png";
+
+    private final Map<ImageResourceCacheKey, ResourceReference> resourceReferenceByKey = Maps.newConcurrentMap();
+    private final PackageResourceReference fallbackResource;
+
+    public ImageResourceCacheClassPath() {
+        fallbackResource = new PackageResourceReference(Images.class, FALLBACK_IMAGE);
+        resourceReferenceByKey.put(new ImageResourceCacheKey(null, null), fallbackResource);
+    }
+
+    @Override
+    public ResourceReference resourceReferenceFor(final ObjectAdapter adapter) {
+        return resourceReferenceFor(adapter.getSpecification(), adapter.getIconName());
+    }
+
+    @Override
+    public ResourceReference resourceReferenceForSpec(final ObjectSpecification spec) {
+        return resourceReferenceFor(spec, null);
+    }
+
+    private ResourceReference resourceReferenceFor(
+            final ObjectSpecification specification,
+            final String iconNameIfAny) {
+
+        final Class<?> correspondingClassIfAny = specification != null? specification.getCorrespondingClass(): null;
+        final ImageResourceCacheKey key = new ImageResourceCacheKey(correspondingClassIfAny, iconNameIfAny);
+
+        ResourceReference resourceReference = resourceReferenceByKey.get(key);
+        if(resourceReference == null) {
+            resourceReference = findResourceReferenceFor(key);
+            resourceReferenceByKey.put(key, resourceReference);
+        }
+
+        return resourceReference;
+    }
+
+    protected ResourceReference findResourceReferenceFor(final ImageResourceCacheKey key) {
+        final List<ImageResourceCacheKey> keys = key.resourcePaths(IMAGE_SUFFICES);
+        for (ImageResourceCacheKey resourceKey : keys) {
+            Class<?> resourceClass = resourceKey.getResourceClass();
+            if(resourceClass == null) {
+                resourceClass = Images.class;
+            }
+            final String iconName = resourceKey.getResourceName();
+
+            final InputStream resourceAsStream = resourceClass.getResourceAsStream(iconName);
+            if (resourceAsStream != null) {
+                closeSafely(resourceAsStream);
+                return new PackageResourceReference(resourceClass, iconName);
+            }
+        }
+        return fallbackResource;
+    }
+
+    private static void closeSafely(InputStream resourceAsStream) {
+        try {
+            resourceAsStream.close();
+        } catch (IOException e) {
+            // ignore
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/imagecache/ImageResourceCacheKey.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/imagecache/ImageResourceCacheKey.java b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/imagecache/ImageResourceCacheKey.java
new file mode 100644
index 0000000..68a1ff0
--- /dev/null
+++ b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/imagecache/ImageResourceCacheKey.java
@@ -0,0 +1,193 @@
+/*
+ *  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.viewer.imagecache;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import com.google.common.collect.Lists;
+import org.apache.isis.core.commons.lang.ClassUtil;
+
+final class ImageResourceCacheKey implements Serializable {
+
+    ImageResourceCacheKey(
+            final Class<?> resourceClass,
+            final String resourceName) {
+        this.resourceName = resourceName;
+        this.resourceClass = resourceClass;
+        this.toString = calcToString();
+    }
+
+    //region > getResourceClass, getResourceName
+
+    private final Class<?> resourceClass;
+    private final String resourceName;
+
+    public final Class<?> getResourceClass() {
+        return resourceClass;
+    }
+
+    public final String getResourceName() {
+        return resourceName;
+    }
+    //endregion
+
+    //region > superKey
+
+    ImageResourceCacheKey superKey() {
+        if(resourceClass == null) {
+            return null;
+        }
+        final Class<?> superclass = resourceClass.getSuperclass();
+        if(superclass == Object.class) {
+            return null;
+        }
+        return new ImageResourceCacheKey(superclass, resourceName);
+    }
+
+    //endregion
+
+    //region > resourcePaths
+
+    List<ImageResourceCacheKey> resourcePaths(final String... suffices) {
+        return resourcePaths(Arrays.asList(suffices));
+    }
+
+    List<ImageResourceCacheKey> resourcePaths(final List<String> suffices) {
+
+        ImageResourceCacheKey key;
+        final List<ImageResourceCacheKey> resourcePaths = Lists.newArrayList();
+
+
+        boolean generated = false;
+
+        if(resourceName != null) {
+
+            final Class<?> resourceClass = ClassUtil.forNameElseNull(resourceName);
+
+            if(resourceClass != null) {
+                for (String suffix : suffices) {
+                    resourcePaths.add(new ImageResourceCacheKey(resourceClass, resourceClass.getSimpleName() + "." + suffix));
+                }
+                generated = true;
+            }
+        }
+
+        if(!generated && resourceClass != null) {
+            // with the iconName
+            if(resourceName != null) {
+                key = this;
+                do {
+                    for (String suffix : suffices) {
+                        resourcePaths.add(new ImageResourceCacheKey(key.getResourceClass(), key.getResourceClass().getSimpleName() + "-" + resourceName + "." + suffix));
+                    }
+                    for (String suffix : suffices) {
+                        resourcePaths.add(new ImageResourceCacheKey(key.getResourceClass(), resourceName + "." + suffix));
+                    }
+                } while ((key = key.superKey()) != null);
+                key = this;
+                do {
+                    for (String suffix : suffices) {
+                        resourcePaths.add(new ImageResourceCacheKey(null, key.getResourceClass().getSimpleName() + "-" + resourceName + "." + suffix));
+                    }
+                } while ((key = key.superKey()) != null);
+                for (String suffix : suffices) {
+                    resourcePaths.add(new ImageResourceCacheKey(null, resourceName + "." + suffix));
+                }
+            }
+
+            // without the iconName
+            key = this;
+            do {
+                for (String suffix : suffices) {
+                    resourcePaths.add(new ImageResourceCacheKey(key.getResourceClass(), key.getResourceClass().getSimpleName() + "." + suffix));
+                }
+            } while ((key = key.superKey()) != null);
+            key = this;
+            do {
+                for (String suffix : suffices) {
+                    resourcePaths.add(new ImageResourceCacheKey(null, key.getResourceClass().getSimpleName() + "." + suffix));
+                }
+            } while ((key = key.superKey()) != null);
+
+            generated = true;
+        }
+
+        if(!generated && resourceName != null) {
+
+            for (String suffix : suffices) {
+                resourcePaths.add(new ImageResourceCacheKey(null, resourceName + "." + suffix));
+            }
+
+        }
+
+        // fallback
+        for (String suffix : suffices) {
+            resourcePaths.add(new ImageResourceCacheKey(null, "Default" + "." + suffix));
+        }
+
+
+        return Collections.unmodifiableList(resourcePaths);
+    }
+
+    //endregion
+
+    //region > equals, hashCode
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        ImageResourceCacheKey key = (ImageResourceCacheKey) o;
+        if (toString != null ? !toString.equals(key.toString) : key.toString != null) return false;
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return toString != null ? toString.hashCode() : 0;
+    }
+
+    //endregion
+
+    //region > toString
+
+    private final String toString;
+
+    @Override
+    public String toString() {
+        return toString;
+    }
+
+    private String calcToString() {
+        final StringBuilder buf = new StringBuilder();
+        if(resourceClass != null) {
+            buf.append(resourceClass.getName()).append("/").append(resourceClass.getSimpleName());
+        }
+        if(buf.length() > 0) {
+            buf.append("-");
+        }
+        if(resourceName != null) {
+            buf.append(resourceName);
+        }
+        return buf.toString();
+    }
+    //endregion
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/isis/DeploymentTypeAbstract.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/isis/DeploymentTypeAbstract.java b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/isis/DeploymentTypeAbstract.java
new file mode 100644
index 0000000..cfca7c6
--- /dev/null
+++ b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/isis/DeploymentTypeAbstract.java
@@ -0,0 +1,37 @@
+/*
+ *  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.viewer.integration.isis;
+
+import org.apache.isis.core.metamodel.deployment.DeploymentCategory;
+import org.apache.isis.core.runtime.system.ContextCategory;
+import org.apache.isis.core.runtime.system.DeploymentType;
+
+/**
+ * Simple adapter for Isis' {@link DeploymentType} class, specifying that the
+ * {@link ContextCategory#THREADLOCAL} is used as the
+ * {@link ContextCategory}.
+ */
+public abstract class DeploymentTypeAbstract extends DeploymentType {
+
+    public DeploymentTypeAbstract(final String name, final DeploymentCategory category) {
+        super(name, category, ContextCategory.THREADLOCAL);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/isis/DeploymentTypeWicketAbstract.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/isis/DeploymentTypeWicketAbstract.java b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/isis/DeploymentTypeWicketAbstract.java
new file mode 100644
index 0000000..dcefd4c
--- /dev/null
+++ b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/isis/DeploymentTypeWicketAbstract.java
@@ -0,0 +1,49 @@
+/*
+ *  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.viewer.integration.isis;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.RuntimeConfigurationType;
+
+import org.apache.isis.core.metamodel.deployment.DeploymentCategory;
+import org.apache.isis.core.runtime.system.DeploymentType;
+import org.apache.isis.viewer.wicket.viewer.IsisWicketApplication;
+
+/**
+ * {@link DeploymentType} for production usage.
+ * 
+ * <p>
+ * In {@link IsisWicketApplication} there is logic to select either this 
+ * {@link DeploymentType} or {@link WicketServerPrototype} depending on the 
+ * value of Wicket's own {@link Application#getConfigurationType() configuration mode}. 
+ */
+public class DeploymentTypeWicketAbstract extends DeploymentTypeAbstract {
+
+    public DeploymentTypeWicketAbstract(
+            final String name, final DeploymentCategory deploymentCategory) {
+        super(name, deploymentCategory);
+    }
+
+    public RuntimeConfigurationType getConfigurationType() {
+        return getDeploymentCategory().isProduction()? RuntimeConfigurationType.DEPLOYMENT: RuntimeConfigurationType.DEVELOPMENT;
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/isis/WicketServer.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/isis/WicketServer.java b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/isis/WicketServer.java
new file mode 100644
index 0000000..da1cf7a
--- /dev/null
+++ b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/isis/WicketServer.java
@@ -0,0 +1,42 @@
+/*
+ *  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.viewer.integration.isis;
+
+import org.apache.wicket.Application;
+
+import org.apache.isis.core.metamodel.deployment.DeploymentCategory;
+import org.apache.isis.core.runtime.system.DeploymentType;
+import org.apache.isis.viewer.wicket.viewer.IsisWicketApplication;
+
+/**
+ * {@link DeploymentType} for production usage.
+ * 
+ * <p>
+ * In {@link IsisWicketApplication} there is logic to select either this 
+ * {@link DeploymentType} or {@link WicketServerPrototype} depending on the value 
+ * of Wicket's own {@link Application#getConfigurationType() configuration mode}. 
+ */
+public class WicketServer extends DeploymentTypeWicketAbstract {
+
+    public WicketServer() {
+        super("WICKET_SERVER", DeploymentCategory.PRODUCTION);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/isis/WicketServerPrototype.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/isis/WicketServerPrototype.java b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/isis/WicketServerPrototype.java
new file mode 100644
index 0000000..5878c5d
--- /dev/null
+++ b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/isis/WicketServerPrototype.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.viewer.integration.isis;
+
+import org.apache.isis.core.metamodel.deployment.DeploymentCategory;
+import org.apache.isis.core.runtime.system.DeploymentType;
+import org.apache.isis.viewer.wicket.viewer.IsisWicketApplication;
+
+/**
+ * {@link DeploymentType} for prototype usage.
+ * 
+ * <p>
+ * In {@link IsisWicketApplication} there is logic to select either this 
+ * {@link DeploymentType} or {@link WicketServer} depending on the value of 
+ * Wicket's own {@link org.apache.wicket.Application#getConfigurationType() configuration mode}.
+ */
+public class WicketServerPrototype extends DeploymentTypeWicketAbstract {
+
+    public WicketServerPrototype() {
+        super("WICKET_SERVER_PROTOTYPE", DeploymentCategory.PROTOTYPING);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/AnonymousWebSessionForIsis.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/AnonymousWebSessionForIsis.java b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/AnonymousWebSessionForIsis.java
new file mode 100644
index 0000000..d5686e0
--- /dev/null
+++ b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/AnonymousWebSessionForIsis.java
@@ -0,0 +1,46 @@
+/*
+ *  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.viewer.integration.wicket;
+
+import org.apache.wicket.Session;
+import org.apache.wicket.request.Request;
+
+import org.apache.isis.core.runtime.authentication.AuthenticationManager;
+
+public class AnonymousWebSessionForIsis extends AuthenticatedWebSessionForIsis {
+
+    private static final long serialVersionUID = 1L;
+
+    public static AnonymousWebSessionForIsis get() {
+        return (AnonymousWebSessionForIsis) Session.get();
+    }
+
+    private final AuthenticationManager authenticationManager;
+
+    public AnonymousWebSessionForIsis(final Request request, final AuthenticationManager authenticationManager) {
+        super(request);
+        this.authenticationManager = authenticationManager;
+    }
+
+    @Override
+    protected AuthenticationManager getAuthenticationManager() {
+        return authenticationManager;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/AuthenticatedWebSessionForIsis.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/AuthenticatedWebSessionForIsis.java b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/AuthenticatedWebSessionForIsis.java
new file mode 100644
index 0000000..05db776
--- /dev/null
+++ b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/AuthenticatedWebSessionForIsis.java
@@ -0,0 +1,183 @@
+/*
+ *  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.viewer.integration.wicket;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Callable;
+import org.apache.wicket.Session;
+import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
+import org.apache.wicket.authroles.authorization.strategies.role.Roles;
+import org.apache.wicket.request.Request;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.isis.applib.clock.Clock;
+import org.apache.isis.applib.services.session.SessionLoggingService;
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
+import org.apache.isis.core.commons.authentication.AuthenticationSessionProviderAware;
+import org.apache.isis.core.commons.ensure.Ensure;
+import org.apache.isis.core.runtime.authentication.AuthenticationManager;
+import org.apache.isis.core.runtime.authentication.AuthenticationRequest;
+import org.apache.isis.core.runtime.authentication.AuthenticationRequestPassword;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.wicket.model.models.BookmarkedPagesModel;
+import org.apache.isis.viewer.wicket.ui.components.widgets.breadcrumbs.BreadcrumbModel;
+import org.apache.isis.viewer.wicket.ui.components.widgets.breadcrumbs.BreadcrumbModelProvider;
+import org.apache.isis.viewer.wicket.ui.pages.BookmarkedPagesModelProvider;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.nullValue;
+
+/**
+ * Viewer-specific implementation of {@link AuthenticatedWebSession}, which
+ * delegates to the Isis' configured {@link AuthenticationManager}, and which
+ * also tracks thread usage (so that multiple concurrent requests are all
+ * associated with the same session).
+ */
+public class AuthenticatedWebSessionForIsis extends AuthenticatedWebSession implements AuthenticationSessionProvider, BreadcrumbModelProvider, BookmarkedPagesModelProvider {
+
+    private static final long serialVersionUID = 1L;
+
+    public static final String USER_ROLE = "org.apache.isis.viewer.wicket.roles.USER";
+
+    public static AuthenticatedWebSessionForIsis get() {
+        return (AuthenticatedWebSessionForIsis) Session.get();
+    }
+
+    private final BookmarkedPagesModel bookmarkedPagesModel = new BookmarkedPagesModel();
+    private final BreadcrumbModel breadcrumbModel = new BreadcrumbModel();
+    
+    private AuthenticationSession authenticationSession;
+
+    public AuthenticatedWebSessionForIsis(final Request request) {
+        super(Ensure.ensureThatArg(request, is(not(nullValue(Request.class)))));
+    }
+
+    @Override
+    public boolean authenticate(final String username, final String password) {
+        AuthenticationRequest authenticationRequest = new AuthenticationRequestPassword(username, password);
+        authenticationRequest.setRoles(Arrays.asList(USER_ROLE));
+        authenticationSession = getAuthenticationManager().authenticate(authenticationRequest);
+        if (authenticationSession != null) {
+            log(SessionLoggingService.Type.LOGIN, username, null);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public void onInvalidate() {
+        super.onInvalidate();
+
+        SessionLoggingService.CausedBy causedBy = RequestCycle.get() != null
+                ? SessionLoggingService.CausedBy.USER
+                : SessionLoggingService.CausedBy.SESSION_EXPIRATION;
+
+        String userName = null;
+        if (authenticationSession != null) {
+            userName = authenticationSession.getUserName();
+        }
+
+        log(SessionLoggingService.Type.LOGOUT, userName, causedBy);
+    }
+
+    @Override
+    public AuthenticationSession getAuthenticationSession() {
+        return authenticationSession;
+    }
+
+    @Override
+    public Roles getRoles() {
+        if (!isSignedIn()) {
+            return null;
+        }
+        final List<String> roles = authenticationSession.getRoles();
+        return new Roles(roles.toArray(new String[roles.size()]));
+    }
+    
+    // /////////////////////////////////////////////////
+    // Breadcrumbs and Bookmarks support
+    // /////////////////////////////////////////////////
+
+    @Override
+    public BreadcrumbModel getBreadcrumbModel() {
+        return breadcrumbModel;
+    }
+
+    @Override
+    public BookmarkedPagesModel getBookmarkedPagesModel() {
+        return bookmarkedPagesModel;
+    }
+
+
+    // /////////////////////////////////////////////////
+    // Dependencies
+    // /////////////////////////////////////////////////
+
+    protected AuthenticationManager getAuthenticationManager() {
+        return IsisContext.getAuthenticationManager();
+    }
+
+    // /////////////////////////////////////////////////
+    // *Provider impl.
+    // /////////////////////////////////////////////////
+    
+    @Override
+    public void injectInto(final Object candidate) {
+        if (AuthenticationSessionProviderAware.class.isAssignableFrom(candidate.getClass())) {
+            final AuthenticationSessionProviderAware cast = AuthenticationSessionProviderAware.class.cast(candidate);
+            cast.setAuthenticationSessionProvider(this);
+        }
+    }
+
+    private void log(
+            final SessionLoggingService.Type type,
+            final String username,
+            final SessionLoggingService.CausedBy causedBy) {
+        final SessionLoggingService sessionLoggingService = getSessionLoggingService();
+        if (sessionLoggingService != null) {
+            IsisContext.doInSession(new Runnable() {
+                @Override
+                public void run() {
+                    // use hashcode as session identifier, to avoid re-binding http sessions if using Session#getId()
+                    int sessionHashCode = System.identityHashCode(AuthenticatedWebSessionForIsis.this);
+                    sessionLoggingService.log(type, username, Clock.getTimeAsDateTime().toDate(), causedBy, Integer.toString(sessionHashCode));
+                }
+            });
+        }
+    }
+
+    protected SessionLoggingService getSessionLoggingService() {
+        return IsisContext.doInSession(new Callable<SessionLoggingService>() {
+            @Override
+            public SessionLoggingService call() throws Exception {
+                return IsisContext.getPersistenceSession().getServicesInjector().lookupService(SessionLoggingService.class);
+            }
+        });
+    }
+
+    @Override
+    public void replaceSession() {
+        // do nothing here because this will lead to problems with Shiro
+        // see https://issues.apache.org/jira/browse/ISIS-1018
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/ConverterForObjectAdapter.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/ConverterForObjectAdapter.java b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/ConverterForObjectAdapter.java
new file mode 100644
index 0000000..940a1ef
--- /dev/null
+++ b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/ConverterForObjectAdapter.java
@@ -0,0 +1,79 @@
+/*
+ *  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.viewer.integration.wicket;
+
+import java.util.Locale;
+
+import org.apache.wicket.util.convert.IConverter;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
+import org.apache.isis.core.metamodel.adapter.oid.Oid;
+import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller;
+import org.apache.isis.core.metamodel.adapter.oid.RootOidDefault;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+
+/**
+ * Implementation of a Wicket {@link IConverter} for {@link ObjectAdapter}s,
+ * converting to-and-from their {@link Oid}'s string representation.
+ */
+public class ConverterForObjectAdapter implements IConverter<ObjectAdapter> {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Converts string representation of {@link Oid} to
+     * {@link ObjectAdapter}.
+     */
+    @Override
+    public ObjectAdapter convertToObject(final String value, final Locale locale) {
+        final Oid oid = RootOidDefault.deStringEncoded(value, getOidMarshaller());
+        return getAdapterManager().getAdapterFor(oid);
+    }
+
+    /**
+     * Converts {@link ObjectAdapter} to string representation of {@link Oid}.
+     */
+    @Override
+    public String convertToString(final ObjectAdapter adapter, final Locale locale) {
+        final Oid oid = adapter.getOid();
+        if (oid == null) {
+            // values don't have an Oid
+            return null;
+        }
+        
+        return oid.enString(getOidMarshaller());
+    }
+    
+
+
+    // //////////////////////////////////////////////////////////
+    // Dependencies (from context)
+    // //////////////////////////////////////////////////////////
+
+    protected AdapterManager getAdapterManager() {
+        return IsisContext.getPersistenceSession().getAdapterManager();
+    }
+
+    protected OidMarshaller getOidMarshaller() {
+        return IsisContext.getOidMarshaller();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/ConverterForObjectAdapterMemento.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/ConverterForObjectAdapterMemento.java b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/ConverterForObjectAdapterMemento.java
new file mode 100644
index 0000000..7de6544
--- /dev/null
+++ b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/ConverterForObjectAdapterMemento.java
@@ -0,0 +1,98 @@
+/*
+ *  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.viewer.integration.wicket;
+
+import java.util.Locale;
+
+import com.google.common.base.Strings;
+
+import org.apache.wicket.util.convert.IConverter;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
+import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager.ConcurrencyChecking;
+import org.apache.isis.core.metamodel.adapter.oid.Oid;
+import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller;
+import org.apache.isis.core.metamodel.adapter.oid.RootOid;
+import org.apache.isis.core.metamodel.adapter.oid.RootOidDefault;
+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.mementos.ObjectAdapterMemento;
+
+/**
+ * Implementation of a Wicket {@link IConverter} for
+ * {@link ObjectAdapterMemento}s, converting to-and-from their {@link Oid}'s
+ * string representation.
+ */
+public class ConverterForObjectAdapterMemento implements IConverter<ObjectAdapterMemento> {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Converts string representation of {@link Oid} to
+     * {@link ObjectAdapterMemento}.
+     */
+    @Override
+    public ObjectAdapterMemento convertToObject(final String value, final Locale locale) {
+        if (Strings.isNullOrEmpty(value)) {
+            return null;
+        }
+        final Oid oid = RootOidDefault.deStringEncoded(value, getOidMarshaller());
+        final ObjectAdapter adapter = getAdapterManager().getAdapterFor(oid);
+        return ObjectAdapterMemento.createOrNull(adapter);
+    }
+
+    /**
+     * Converts {@link ObjectAdapterMemento} to string representation of
+     * {@link RootOid}.
+     */
+    @Override
+    public String convertToString(final ObjectAdapterMemento memento, final Locale locale) {
+        if (memento == null) {
+            return null;
+        }
+        final Oid oid = memento.getObjectAdapter(ConcurrencyChecking.NO_CHECK).getOid();
+        if (oid == null) {
+            // values don't have an Oid...
+            // REVIEW: is this right?
+            return memento.toString();
+        }
+        return oid.enString(getOidMarshaller());
+    }
+    
+
+
+    // //////////////////////////////////////////////////////////
+    // Dependencies (from context)
+    // //////////////////////////////////////////////////////////
+
+    protected AdapterManager getAdapterManager() {
+        return getPersistenceSession().getAdapterManager();
+    }
+
+    protected PersistenceSession getPersistenceSession() {
+        return IsisContext.getPersistenceSession();
+    }
+
+    protected OidMarshaller getOidMarshaller() {
+        return IsisContext.getOidMarshaller();
+    }
+
+}