You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2021/06/25 06:08:21 UTC

[isis] branch master updated: ISIS-2766: make ObjectIcon(s) available via ManagedObject(s) directly

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new 109c098  ISIS-2766: make ObjectIcon(s) available via ManagedObject(s) directly
109c098 is described below

commit 109c0989ec3729ee5a9772f28cb8039af9d1d35a
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Jun 25 08:08:07 2021 +0200

    ISIS-2766: make ObjectIcon(s) available via ManagedObject(s) directly
    
    refactor the ObjectIconService to a MM internal service
    
    add icon image data memoization
---
 .../isis/commons/internal/base/_Strings.java       |  47 +++++-----
 .../_testing/MetaModelContext_forTesting.java      |   3 +
 .../metamodel/context/HasMetaModelContext.java     |  13 ++-
 .../core/metamodel/context/MetaModelContext.java   |   8 +-
 .../context/MetaModelContext_usingIoc.java         |  11 ++-
 .../metamodel/facets/object/icon/ObjectIcon.java   |  95 +++++++++++++++++++++
 .../facets/object/icon/ObjectIconService.java      |  62 ++++++++++++++
 .../isis/core/metamodel/spec/ManagedObject.java    |  29 ++++---
 .../core/metamodel/spec/ObjectSpecification.java   |   3 +
 .../specimpl/ObjectSpecificationAbstract.java      |  20 ++++-
 .../testspec/ObjectSpecificationStub.java          |  32 ++++---
 .../IsisModuleCoreRuntimeServices.java             |   2 +
 .../homepage/HomePageResolverServiceDefault.java   |   4 +-
 .../icons}/ObjectIconServiceDefault.java           |  20 +++--
 .../runtimeservices/icons}/ObjectIconFallback.png  | Bin
 .../common/model/IsisModuleViewerCommon.java       |   2 -
 .../common/model/decorator/icon/ObjectIcon.java    |  53 ------------
 .../model/decorator/icon/ObjectIconService.java    |  51 -----------
 .../resources/DomainObjectResourceServerside.java  |   9 +-
 .../services/ImageResourceCacheClassPath.java      |  12 +--
 20 files changed, 284 insertions(+), 192 deletions(-)

diff --git a/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java b/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java
index dcbc44f..e435385 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java
@@ -54,7 +54,6 @@ import static org.apache.isis.commons.internal.functions._Predicates.not;
 
 import lombok.NonNull;
 import lombok.SneakyThrows;
-import lombok.val;
 
 /**
  * <h1>- internal use only -</h1>
@@ -98,13 +97,13 @@ public final class _Strings {
      * @return a non-empty Optional, if (and only if) the {@code keyValueLiteral}
      * does contain at least one {@code separator}
      */
-    public static Optional<KeyValuePair> parseKeyValuePair(@Nullable String keyValueLiteral, char separator) {
+    public static Optional<KeyValuePair> parseKeyValuePair(@Nullable final String keyValueLiteral, final char separator) {
         return _Strings_KeyValuePair.parse(keyValueLiteral, separator);
     }
 
     // -- FILLING
 
-    public static String of(int length, char c) {
+    public static String of(final int length, final char c) {
         if(length<=0) {
             return "";
         }
@@ -210,7 +209,7 @@ public final class _Strings {
      * @param input
      * @return null if the {@code input} is null or empty, the {@code input} otherwise
      */
-    public static @Nullable String emptyToNull(@Nullable String input) {
+    public static @Nullable String emptyToNull(@Nullable final String input) {
         if(isEmpty(input)) {
             return null;
         }
@@ -221,7 +220,7 @@ public final class _Strings {
      * @param input
      * @return the empty string if the {@code input} is null, the {@code input} otherwise
      */
-    public static String nullToEmpty(@Nullable String input) {
+    public static String nullToEmpty(@Nullable final String input) {
         if(input==null) {
             return "";
         }
@@ -234,7 +233,7 @@ public final class _Strings {
      * @param input
      * @return null if the {@code input} is null
      */
-    public static String trim(@Nullable String input) {
+    public static String trim(@Nullable final String input) {
         return mapIfPresentElse(input, String::trim, null);
     }
 
@@ -294,7 +293,7 @@ public final class _Strings {
 
     // -- SPECIAL UNARY OPERATORS
 
-    public static String htmlEscape(String source) {
+    public static String htmlEscape(final String source) {
         return _Strings_HtmlEscaper.htmlEscape(source);
     }
 
@@ -348,7 +347,7 @@ public final class _Strings {
      * @return non-null
      */
     public static String combineWithDelimiter(
-            @Nullable String left, @Nullable String right, String delimiter) {
+            @Nullable final String left, @Nullable final String right, final String delimiter) {
 
         requiresNotEmpty(delimiter, "pathDelimiter");
 
@@ -377,7 +376,7 @@ public final class _Strings {
      * @param minLength
      * @param c
      */
-    public static String padStart(@Nullable String str, int minLength, char c) {
+    public static String padStart(@Nullable final String str, final int minLength, final char c) {
         if(minLength<=0) {
             return str;
         }
@@ -398,7 +397,7 @@ public final class _Strings {
      * @param minLength
      * @param c
      */
-    public static String padEnd(@Nullable String str, int minLength, char c) {
+    public static String padEnd(@Nullable final String str, final int minLength, final char c) {
         if(minLength<=0) {
             return str;
         }
@@ -450,7 +449,7 @@ public final class _Strings {
      * @param input
      * @param delimiterPattern
      */
-    public static Stream<String> splitThenStream(@Nullable final CharSequence input, Pattern delimiterPattern) {
+    public static Stream<String> splitThenStream(@Nullable final CharSequence input, final Pattern delimiterPattern) {
         if(isEmpty(input)) {
             return Stream.of();
         }
@@ -518,7 +517,7 @@ public final class _Strings {
                 .map(s->s.replace("\r", ""));
     }
 
-    public static Stream<String> grep(@Nullable final String input, @Nullable String contains){
+    public static Stream<String> grep(@Nullable final String input, @Nullable final String contains){
         final Predicate<String> matcher = contains!=null ? line->line.contains(contains) : _Predicates.alwaysTrue();
         return grep(input, matcher);
     }
@@ -586,7 +585,7 @@ public final class _Strings {
 
     // -- READ FROM INPUT STREAM
 
-    public static String read(@Nullable final InputStream input, Charset charset) {
+    public static String read(@Nullable final InputStream input, final Charset charset) {
         requires(charset, "charset");
         if(input==null) {
             return "";
@@ -603,7 +602,7 @@ public final class _Strings {
             final @NonNull Class<?> resourceLocation,
             final @NonNull String resourceName,
             final @NonNull Charset charset) {
-        try(val input = resourceLocation.getResourceAsStream(resourceName)){
+        try(lombok.val input = resourceLocation.getResourceAsStream(resourceName)){
             return read(input, charset);
         }
     }
@@ -616,7 +615,7 @@ public final class _Strings {
      * @param charset
      * @return null if {@code str} is null
      */
-    public static final byte[] toBytes(@Nullable final String str, Charset charset) {
+    public static final byte[] toBytes(@Nullable final String str, final Charset charset) {
         requires(charset, "charset");
         return mapIfPresentElse(str, __->str.getBytes(charset), null);
     }
@@ -627,7 +626,7 @@ public final class _Strings {
      * @param charset
      * @return null if {@code bytes} is null
      */
-    public static final String ofBytes(@Nullable final byte[] bytes, Charset charset) {
+    public static final String ofBytes(@Nullable final byte[] bytes, final Charset charset) {
         requires(charset, "charset");
         return mapIfPresentElse(bytes, __->new String(bytes, charset), null);
     }
@@ -656,15 +655,15 @@ public final class _Strings {
 
         private final UnaryOperator<String> operator;
 
-        private StringOperator(UnaryOperator<String> operator) {
+        private StringOperator(final UnaryOperator<String> operator) {
             this.operator = requires(operator, "operator");
         }
 
-        public String apply(String input) {
+        public String apply(final String input) {
             return operator.apply(input);
         }
 
-        public StringOperator andThen(UnaryOperator<String> andThen) {
+        public StringOperator andThen(final UnaryOperator<String> andThen) {
             return new StringOperator(s->andThen.apply(operator.apply(s)));
         }
 
@@ -692,7 +691,7 @@ public final class _Strings {
             .andThen(s->_Strings_NaturalNames.naturalName2(s, true));
 
 
-    public static final String asFileNameWithExtension(final String fileName, String fileExtension) {
+    public static final String asFileNameWithExtension(final String fileName, final String fileExtension) {
         requires(fileName, "fileName");
         requires(fileExtension, "fileExtension");
         return suffix(fileName, prefix(fileExtension, "."));
@@ -705,7 +704,7 @@ public final class _Strings {
      * empty chunks.
      * @return empty stream if {@code input} is null
      */
-    public static Stream<String> splitThenStreamTrimmed(@Nullable String input, String separator) {
+    public static Stream<String> splitThenStreamTrimmed(@Nullable final String input, final String separator) {
         return splitThenStream(input, separator)
                 .map(String::trim)
                 .filter(not(String::isEmpty));
@@ -716,17 +715,17 @@ public final class _Strings {
      * then discarding empty chunks.
      * @return empty stream if {@code input} is null
      */
-    public static Stream<String> splitThenStreamTrimmed(@Nullable CharSequence input, Pattern delimiterPattern) {
+    public static Stream<String> splitThenStreamTrimmed(@Nullable final CharSequence input, final Pattern delimiterPattern) {
         return splitThenStream(input, delimiterPattern)
                 .map(String::trim)
                 .filter(not(String::isEmpty));
     }
 
-    public static String base64UrlDecode(final String str) {
+    public static String base64UrlDecode(final @Nullable String str) {
         return _Strings.convert(str, _Bytes.ofUrlBase64, StandardCharsets.UTF_8);
     }
 
-    public static String base64UrlEncode(final String str) {
+    public static String base64UrlEncode(final @Nullable String str) {
         return _Strings.convert(str, _Bytes.asUrlBase64, StandardCharsets.UTF_8);
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/_testing/MetaModelContext_forTesting.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/_testing/MetaModelContext_forTesting.java
index 11c4e64..c677837 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/_testing/MetaModelContext_forTesting.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/_testing/MetaModelContext_forTesting.java
@@ -53,6 +53,7 @@ import org.apache.isis.core.config.beans.IsisBeanTypeRegistryDefault;
 import org.apache.isis.core.config.environment.IsisSystemEnvironment;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.execution.MemberExecutorService;
+import org.apache.isis.core.metamodel.facets.object.icon.ObjectIconService;
 import org.apache.isis.core.metamodel.objectmanager.ObjectManager;
 import org.apache.isis.core.metamodel.objectmanager.ObjectManagerDefault;
 import org.apache.isis.core.metamodel.progmodel.ProgrammingModel;
@@ -118,6 +119,8 @@ public final class MetaModelContext_forTesting implements MetaModelContext {
     @Builder.Default
     private TitleService titleService = new TitleServiceDefault(null, null);
 
+    private ObjectIconService objectIconService;
+
     private RepositoryService repositoryService;
 
     private FactoryService factoryService;
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/context/HasMetaModelContext.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/context/HasMetaModelContext.java
index e634532..8b402fb 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/context/HasMetaModelContext.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/context/HasMetaModelContext.java
@@ -33,6 +33,7 @@ import org.apache.isis.applib.services.xactn.TransactionService;
 import org.apache.isis.core.config.IsisConfiguration;
 import org.apache.isis.core.config.environment.IsisSystemEnvironment;
 import org.apache.isis.core.metamodel.execution.MemberExecutorService;
+import org.apache.isis.core.metamodel.facets.object.icon.ObjectIconService;
 import org.apache.isis.core.metamodel.objectmanager.ObjectManager;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
@@ -97,11 +98,11 @@ public interface HasMetaModelContext {
         return getMetaModelContext().getTitleService();
     }
 
-    default Optional<ObjectSpecification> specForType(Class<?> type) {
+    default Optional<ObjectSpecification> specForType(final Class<?> type) {
         return getMetaModelContext().specForType(type);
     }
 
-    default ObjectSpecification specForTypeElseFail(Class<?> type) {
+    default ObjectSpecification specForTypeElseFail(final Class<?> type) {
         return getMetaModelContext().specForTypeElseFail(type);
     }
 
@@ -117,6 +118,10 @@ public interface HasMetaModelContext {
         return getMetaModelContext().getTransactionService();
     }
 
+    default ObjectIconService getObjectIconService() {
+        return getMetaModelContext().getObjectIconService();
+    }
+
     default ObjectManager getObjectManager() {
         return getMetaModelContext().getObjectManager();
     }
@@ -127,11 +132,11 @@ public interface HasMetaModelContext {
 
     // -- ADVANCED SHORTCUTS
 
-    default ManagedObject lookupServiceAdapterById(String serviceId) {
+    default ManagedObject lookupServiceAdapterById(final String serviceId) {
         return getMetaModelContext().lookupServiceAdapterById(serviceId);
     }
 
-    default <T> T getSingletonElseFail(Class<T> type) {
+    default <T> T getSingletonElseFail(final Class<T> type) {
         return getMetaModelContext().getSingletonElseFail(type);
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/context/MetaModelContext.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/context/MetaModelContext.java
index d5f9f47..30d0453 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/context/MetaModelContext.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/context/MetaModelContext.java
@@ -36,6 +36,7 @@ import org.apache.isis.applib.services.xactn.TransactionService;
 import org.apache.isis.core.config.IsisConfiguration;
 import org.apache.isis.core.config.environment.IsisSystemEnvironment;
 import org.apache.isis.core.metamodel.execution.MemberExecutorService;
+import org.apache.isis.core.metamodel.facets.object.icon.ObjectIconService;
 import org.apache.isis.core.metamodel.objectmanager.ObjectManager;
 import org.apache.isis.core.metamodel.objectmanager.load.ObjectLoader;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
@@ -92,6 +93,8 @@ public interface MetaModelContext {
 
     TitleService getTitleService();
 
+    ObjectIconService getObjectIconService();
+
     RepositoryService getRepositoryService();
 
     FactoryService getFactoryService();
@@ -118,7 +121,6 @@ public interface MetaModelContext {
      *
      * @param type
      * @param <T>
-     * @return
      */
     <T> T getSingletonElseFail(Class<T> type);
 
@@ -137,9 +139,11 @@ public interface MetaModelContext {
 
     // -- EXTRACTORS
 
-    public static MetaModelContext from(ManagedObject adapter) {
+    public static MetaModelContext from(final ManagedObject adapter) {
         return adapter.getSpecification().getMetaModelContext();
     }
 
 
+
+
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/context/MetaModelContext_usingIoc.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/context/MetaModelContext_usingIoc.java
index f4cefc3..01c0cd5 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/context/MetaModelContext_usingIoc.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/context/MetaModelContext_usingIoc.java
@@ -40,6 +40,7 @@ import org.apache.isis.commons.internal.ioc._ManagedBeanAdapter;
 import org.apache.isis.core.config.IsisConfiguration;
 import org.apache.isis.core.config.environment.IsisSystemEnvironment;
 import org.apache.isis.core.metamodel.execution.MemberExecutorService;
+import org.apache.isis.core.metamodel.facets.object.icon.ObjectIconService;
 import org.apache.isis.core.metamodel.objectmanager.ObjectManager;
 import org.apache.isis.core.metamodel.services.ServiceUtil;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
@@ -54,7 +55,7 @@ import lombok.val;
 class MetaModelContext_usingIoc implements MetaModelContext {
 
     private final _IocContainer iocContainer;
-    public MetaModelContext_usingIoc(_IocContainer iocContainer) {
+    public MetaModelContext_usingIoc(final _IocContainer iocContainer) {
         this.iocContainer = iocContainer;
     }
 
@@ -99,6 +100,10 @@ class MetaModelContext_usingIoc implements MetaModelContext {
     getSingletonElseFail(InteractionProvider.class);
 
     @Getter(lazy=true)
+    private final ObjectIconService objectIconService =
+    getSingletonElseFail(ObjectIconService.class);
+
+    @Getter(lazy=true)
     private final TitleService titleService =
     getSingletonElseFail(TitleService.class);
 
@@ -153,7 +158,7 @@ class MetaModelContext_usingIoc implements MetaModelContext {
     // -- LOOKUP
 
     @Override
-    public <T> T getSingletonElseFail(Class<T> type) {
+    public <T> T getSingletonElseFail(final Class<T> type) {
         return iocContainer.getSingletonElseFail(type);
     }
 
@@ -171,7 +176,7 @@ class MetaModelContext_usingIoc implements MetaModelContext {
                 .collect(Collectors.toMap(ServiceUtil::idOfAdapter, v->v, (o,n)->n, LinkedHashMap::new));
     }
 
-    private ManagedObject toManagedObject(_ManagedBeanAdapter managedBeanAdapter) {
+    private ManagedObject toManagedObject(final _ManagedBeanAdapter managedBeanAdapter) {
 
         val servicePojo = managedBeanAdapter.getInstance().getFirst()
                 .orElseThrow(()->_Exceptions.unrecoverableFormatted(
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/icon/ObjectIcon.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/icon/ObjectIcon.java
new file mode 100644
index 0000000..030662d
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/icon/ObjectIcon.java
@@ -0,0 +1,95 @@
+/*
+ *  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.metamodel.facets.object.icon;
+
+import java.io.InputStream;
+import java.io.Serializable;
+import java.net.URL;
+
+import org.apache.isis.applib.value.NamedWithMimeType.CommonMimeType;
+import org.apache.isis.commons.internal._Constants;
+import org.apache.isis.commons.internal.base._Bytes;
+import org.apache.isis.commons.internal.base._Strings;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.Synchronized;
+import lombok.ToString;
+import lombok.val;
+
+@RequiredArgsConstructor
+@ToString
+@EqualsAndHashCode
+public class ObjectIcon
+implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Create an ObjectIcon and eagerly read in image data from
+     * class-path resources.
+     */
+    public static ObjectIcon eager(
+            final String shortName,
+            final URL url,
+            final CommonMimeType mimeType) {
+        val id = _Strings.base64UrlEncode(url.getPath());
+        val objectIcon = new ObjectIcon(shortName, url, mimeType, id);
+        objectIcon.asBytes(); // memoize
+        return objectIcon;
+    }
+
+    /**
+     * Create an ObjectIcon and not yet read in image data from
+     * class-path resources.
+     */
+    public static ObjectIcon lazy(
+            final String shortName,
+            final URL url,
+            final CommonMimeType mimeType) {
+        val id = _Strings.base64UrlEncode(url.getPath());
+        return new ObjectIcon(shortName, url, mimeType, id);
+    }
+
+    @Getter private @NonNull String shortName;
+    @Getter private @NonNull URL url;
+    @Getter private @NonNull CommonMimeType mimeType;
+    @Getter private @NonNull String identifier;
+
+    @ToString.Exclude
+    @EqualsAndHashCode.Exclude
+    private transient byte[] iconData;
+
+    @Synchronized
+    public byte[] asBytes() {
+
+        if(iconData==null) {
+            try(final InputStream is = url.openStream()){
+                iconData = _Bytes.of(is);
+            } catch (Exception e) {
+                iconData = _Constants.emptyBytes;
+            }
+        }
+
+        return iconData;
+    }
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/icon/ObjectIconService.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/icon/ObjectIconService.java
new file mode 100644
index 0000000..1145635
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/icon/ObjectIconService.java
@@ -0,0 +1,62 @@
+/*
+ *  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.metamodel.facets.object.icon;
+
+import javax.annotation.Nullable;
+
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+
+import lombok.NonNull;
+
+/**
+ * Creates {@link ObjectIcon}(s), which are class-path resource references.
+ * <p>
+ * Internally used by the metamodel.
+ * Clients should use {@link ObjectSpecification#getIcon(org.apache.isis.core.metamodel.spec.ManagedObject)}.
+ *
+ * @see ObjectSpecification#getIcon(org.apache.isis.core.metamodel.spec.ManagedObject)
+ * @since 2.0
+ */
+public interface ObjectIconService {
+
+    /**
+     * {@link ObjectIcon} for given {@link ObjectSpecification}
+     * and iconNameSuffix.
+     * @return non-null
+     */
+    ObjectIcon getObjectIcon(
+            @NonNull ObjectSpecification specification,
+            @Nullable String iconNameSuffixIfAny);
+
+//    ObjectIcon getObjectFallbackIcon();
+//
+//    // -- SHORTCUTS
+//
+//    default ObjectIcon getObjectIcon(final ObjectSpecification spec) {
+//        return getObjectIcon(spec, null);
+//    }
+//
+//    default ObjectIcon getObjectIcon(final ManagedObject adapter) {
+//        return ManagedObjects.isSpecified(adapter)
+//                ? getObjectIcon(
+//                        adapter.getSpecification(),
+//                        adapter.getSpecification().getIconName(adapter))
+//                : getObjectFallbackIcon();
+//    }
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ManagedObject.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ManagedObject.java
index f96840e..99562cc 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ManagedObject.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ManagedObject.java
@@ -28,6 +28,7 @@ import javax.annotation.Nullable;
 import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.commons.internal.base._Lazy;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
+import org.apache.isis.core.metamodel.facets.object.icon.ObjectIcon;
 import org.apache.isis.core.metamodel.objectmanager.ObjectManager;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 
@@ -90,11 +91,11 @@ public interface ManagedObject {
     /**
      * Used only for (standalone or parented) collections.
      */
-    default public Optional<ObjectSpecification> getElementSpecification() {
+    default Optional<ObjectSpecification> getElementSpecification() {
         return getSpecification().getElementSpecification();
     }
 
-    // -- SHORTCUT - ICON NAME
+    // -- SHORTCUT - ICON
 
     /**
      * Returns the name of an icon to use if this object is to be displayed
@@ -102,10 +103,14 @@ public interface ManagedObject {
      * <p>
      * May return <code>null</code> if no icon is specified.
      */
-    default public String getIconName() {
+    default String getIconName() {
         return getSpecification().getIconName(this);
     }
 
+    default ObjectIcon getIcon() {
+        return getSpecification().getIcon(this);
+    }
+
     // -- FACTORIES
 
     /**
@@ -114,8 +119,8 @@ public interface ManagedObject {
      * @param pojo - might also be a collection of pojos
      */
     public static ManagedObject of(
-            @NonNull ObjectSpecification specification,
-            @Nullable Object pojo) {
+            @NonNull final ObjectSpecification specification,
+            @Nullable final Object pojo) {
 
         ManagedObjects.assertPojoNotManaged(pojo);
         specification.assertPojoCompatible(pojo);
@@ -141,9 +146,9 @@ public interface ManagedObject {
      * Optimized for cases, when the pojo's specification and bookmark are already available.
      */
     public static ManagedObject bookmarked(
-            @NonNull ObjectSpecification specification,
-            @NonNull Object pojo,
-            @NonNull Bookmark bookmark) {
+            @NonNull final ObjectSpecification specification,
+            @NonNull final Object pojo,
+            @NonNull final Bookmark bookmark) {
 
         if(!specification.getCorrespondingClass().isAssignableFrom(pojo.getClass())) {
             throw _Exceptions.illegalArgument(
@@ -163,8 +168,8 @@ public interface ManagedObject {
      * @param pojo
      */
     public static ManagedObject lazy(
-            SpecificationLoader specLoader,
-            Object pojo) {
+            final SpecificationLoader specLoader,
+            final Object pojo) {
         ManagedObjects.assertPojoNotManaged(pojo);
         val adapter = new LazyManagedObject(cls->specLoader.specForType(cls).orElse(null), pojo);
         //ManagedObjects.warnIfAttachedEntity(adapter, "consider using ManagedObject.identified(...) for entity");
@@ -244,7 +249,7 @@ public interface ManagedObject {
 
         private final _Lazy<ObjectSpecification> specification = _Lazy.threadSafe(this::loadSpec);
 
-        public LazyManagedObject(@NonNull Function<Class<?>, ObjectSpecification> specLoader, @NonNull Object pojo) {
+        public LazyManagedObject(@NonNull final Function<Class<?>, ObjectSpecification> specLoader, @NonNull final Object pojo) {
             this.specLoader = specLoader;
             this.pojo = pojo;
         }
@@ -274,4 +279,6 @@ public interface ManagedObject {
 
 
 
+
+
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
index fe48b34..cf12d39 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
@@ -53,6 +53,7 @@ import org.apache.isis.core.metamodel.facets.members.cssclass.CssClassFacet;
 import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet;
 import org.apache.isis.core.metamodel.facets.object.entity.EntityFacet;
 import org.apache.isis.core.metamodel.facets.object.icon.IconFacet;
+import org.apache.isis.core.metamodel.facets.object.icon.ObjectIcon;
 import org.apache.isis.core.metamodel.facets.object.immutable.ImmutableFacet;
 import org.apache.isis.core.metamodel.facets.object.parented.ParentedCollectionFacet;
 import org.apache.isis.core.metamodel.facets.object.parseable.ParseableFacet;
@@ -218,6 +219,8 @@ extends
      */
     String getIconName(ManagedObject object);
 
+    ObjectIcon getIcon(ManagedObject object);
+
     /**
      * Returns this object's navigable parent, if any.
      * @param object
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
index bf31768..10b124d 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
@@ -34,6 +34,7 @@ import org.apache.isis.applib.id.LogicalType;
 import org.apache.isis.applib.services.metamodel.BeanSort;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.collections.ImmutableEnumSet;
+import org.apache.isis.commons.internal.assertions._Assert;
 import org.apache.isis.commons.internal.base._Lazy;
 import org.apache.isis.commons.internal.base._NullSafe;
 import org.apache.isis.commons.internal.base._Strings;
@@ -59,6 +60,7 @@ import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.members.cssclass.CssClassFacet;
 import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet;
 import org.apache.isis.core.metamodel.facets.object.icon.IconFacet;
+import org.apache.isis.core.metamodel.facets.object.icon.ObjectIcon;
 import org.apache.isis.core.metamodel.facets.object.immutable.ImmutableFacet;
 import org.apache.isis.core.metamodel.facets.object.logicaltype.LogicalTypeFacet;
 import org.apache.isis.core.metamodel.facets.object.mixin.MixinFacet;
@@ -72,6 +74,7 @@ import org.apache.isis.core.metamodel.interactions.ObjectTitleContext;
 import org.apache.isis.core.metamodel.interactions.ObjectValidityContext;
 import org.apache.isis.core.metamodel.spec.ActionType;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.metamodel.spec.ManagedObjects;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.MixedIn;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
@@ -402,7 +405,6 @@ implements ObjectSpecification {
         updateFromFacetValues();
     }
 
-
     @Override
     public String getTitle(
             final Predicate<ManagedObject> isContextAdapter,
@@ -417,17 +419,27 @@ implements ObjectSpecification {
         return (this.isManagedBean() ? "" : "Untitled ") + getSingularName();
     }
 
+    @Override
+    public String getIconName(final ManagedObject domainObject) {
+
+        if(ManagedObjects.isSpecified(domainObject)) {
+            _Assert.assertEquals(domainObject.getSpecification(), this);
+        }
+
+        return iconFacet == null ? null : iconFacet.iconName(domainObject);
+    }
 
     @Override
-    public String getIconName(final ManagedObject reference) {
-        return iconFacet == null ? null : iconFacet.iconName(reference);
+    public ObjectIcon getIcon(final ManagedObject domainObject) {
+        val iconNameModifier = getIconName(domainObject);
+        return getObjectIconService().getObjectIcon(this, iconNameModifier);
     }
 
     @Override
     public Object getNavigableParent(final Object object) {
         return navigableParentFacet == null
                 ? null
-                        : navigableParentFacet.navigableParent(object);
+                : navigableParentFacet.navigableParent(object);
     }
 
     @Override
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
index 1078a9b..be33f4e 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
@@ -38,6 +38,7 @@ import org.apache.isis.core.metamodel.consent.InteractionResult;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.facetapi.FacetHolderAbstract;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
+import org.apache.isis.core.metamodel.facets.object.icon.ObjectIcon;
 import org.apache.isis.core.metamodel.facets.object.immutable.ImmutableFacet;
 import org.apache.isis.core.metamodel.facets.object.logicaltype.LogicalTypeFacet;
 import org.apache.isis.core.metamodel.interactions.ObjectTitleContext;
@@ -145,7 +146,12 @@ implements ObjectSpecification {
     }
 
     @Override
-    public Object getNavigableParent(Object object) {
+    public ObjectIcon getIcon(final ManagedObject object) {
+        return null;
+    }
+
+    @Override
+    public Object getNavigableParent(final Object object) {
         return null;
     }
 
@@ -277,18 +283,18 @@ implements ObjectSpecification {
 
     @Override
     public ObjectValidityContext createValidityInteractionContext(
-            ManagedObject targetAdapter, InteractionInitiatedBy interactionInitiatedBy) {
+            final ManagedObject targetAdapter, final InteractionInitiatedBy interactionInitiatedBy) {
         return null;
     }
 
     @Override
     public ObjectTitleContext createTitleInteractionContext(
-            ManagedObject targetObjectAdapter, InteractionInitiatedBy invocationMethod) {
+            final ManagedObject targetObjectAdapter, final InteractionInitiatedBy invocationMethod) {
         return null;
     }
 
     @Override
-    public InteractionResult isValidResult(ManagedObject targetAdapter, InteractionInitiatedBy interactionInitiatedBy) {
+    public InteractionResult isValidResult(final ManagedObject targetAdapter, final InteractionInitiatedBy interactionInitiatedBy) {
         return null;
     }
 
@@ -303,7 +309,7 @@ implements ObjectSpecification {
     }
 
     @Override
-    public Stream<ObjectAction> streamDeclaredActions(ImmutableEnumSet<ActionType> types, MixedIn contributed) {
+    public Stream<ObjectAction> streamDeclaredActions(final ImmutableEnumSet<ActionType> types, final MixedIn contributed) {
         return null;
     }
 
@@ -337,7 +343,7 @@ implements ObjectSpecification {
     }
 
     @Override
-    public void introspectUpTo(IntrospectionState upTo) {
+    public void introspectUpTo(final IntrospectionState upTo) {
         // [2158] not implemented yet
     }
 
@@ -348,39 +354,39 @@ implements ObjectSpecification {
     }
 
     @Override
-    public Optional<? extends ObjectMember> getMember(Method method) {
+    public Optional<? extends ObjectMember> getMember(final Method method) {
         return Optional.empty();
     }
 
     @Override
-    public Optional<ObjectAction> getAction(String id, ActionType type) {
+    public Optional<ObjectAction> getAction(final String id, final ActionType type) {
         // poorly implemented, inheritance not supported
         return getDeclaredAction(id, type);
     }
 
     @Override
     public Stream<ObjectAction> streamActions(
-            ImmutableEnumSet<ActionType> types,
-            MixedIn contributed,
+            final ImmutableEnumSet<ActionType> types,
+            final MixedIn contributed,
             final Consumer<ObjectAction> onActionOverloaded) {
         // poorly implemented, inheritance not supported
         return streamDeclaredActions(contributed);
     }
 
     @Override
-    public Optional<ObjectAssociation> getAssociation(String id) {
+    public Optional<ObjectAssociation> getAssociation(final String id) {
         // poorly implemented, inheritance not supported
         return getDeclaredAssociation(id);
     }
 
     @Override
-    public Stream<ObjectAssociation> streamAssociations(MixedIn contributed) {
+    public Stream<ObjectAssociation> streamAssociations(final MixedIn contributed) {
         // poorly implemented, inheritance not supported
         return streamDeclaredAssociations(contributed);
     }
 
     @Override
-    public Stream<ObjectAction> streamRuntimeActions(MixedIn mixedIn) {
+    public Stream<ObjectAction> streamRuntimeActions(final MixedIn mixedIn) {
         val actionTypes = getMetaModelContext().getSystemEnvironment().isPrototyping()
                 ? ActionType.USER_AND_PROTOTYPE
                 : ActionType.USER_ONLY;
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/IsisModuleCoreRuntimeServices.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/IsisModuleCoreRuntimeServices.java
index cc1bb45..8fec5b6 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/IsisModuleCoreRuntimeServices.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/IsisModuleCoreRuntimeServices.java
@@ -38,6 +38,7 @@ import org.apache.isis.core.runtimeservices.factory.FactoryServiceDefault;
 import org.apache.isis.core.runtimeservices.homepage.HomePageResolverServiceDefault;
 import org.apache.isis.core.runtimeservices.i18n.po.TranslationServicePo;
 import org.apache.isis.core.runtimeservices.i18n.po.TranslationServicePoMenu;
+import org.apache.isis.core.runtimeservices.icons.ObjectIconServiceDefault;
 import org.apache.isis.core.runtimeservices.interaction.InteractionDtoFactoryDefault;
 import org.apache.isis.core.runtimeservices.jaxb.JaxbServiceDefault;
 import org.apache.isis.core.runtimeservices.menubars.MenuBarsLoaderServiceDefault;
@@ -92,6 +93,7 @@ import org.apache.isis.core.runtimeservices.xmlsnapshot.XmlSnapshotServiceDefaul
         MenuBarsLoaderServiceDefault.class,
         MenuBarsServiceBS3.class,
         MessageServiceDefault.class,
+        ObjectIconServiceDefault.class,
         ScratchpadDefault.class,
         TransactionServiceSpring.class,
         UrlEncodingServiceWithCompression.class,
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/homepage/HomePageResolverServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/homepage/HomePageResolverServiceDefault.java
index 0b6ecae..18f1b12 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/homepage/HomePageResolverServiceDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/homepage/HomePageResolverServiceDefault.java
@@ -22,11 +22,12 @@ import java.util.Optional;
 
 import javax.annotation.PostConstruct;
 import javax.inject.Inject;
+import javax.inject.Named;
 
-import org.apache.isis.applib.annotation.PriorityPrecedence;
 import org.springframework.stereotype.Service;
 
 import org.apache.isis.applib.annotation.HomePage;
+import org.apache.isis.applib.annotation.PriorityPrecedence;
 import org.apache.isis.applib.services.factory.FactoryService;
 import org.apache.isis.applib.services.homepage.HomePageResolverService;
 import org.apache.isis.commons.internal.reflection._Annotations;
@@ -36,6 +37,7 @@ import org.apache.isis.core.metamodel.commons.ClassExtensions;
 import lombok.val;
 
 @Service
+@Named("isis.runtimeservices.HomePageResolverServiceDefault")
 @javax.annotation.Priority(PriorityPrecedence.MIDPOINT)
 public class HomePageResolverServiceDefault implements HomePageResolverService {
 
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/decorator/icon/ObjectIconServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/icons/ObjectIconServiceDefault.java
similarity index 92%
rename from viewers/common/src/main/java/org/apache/isis/viewer/common/model/decorator/icon/ObjectIconServiceDefault.java
rename to core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/icons/ObjectIconServiceDefault.java
index 99ec4a1..6476efc 100644
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/decorator/icon/ObjectIconServiceDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/icons/ObjectIconServiceDefault.java
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.viewer.common.model.decorator.icon;
+package org.apache.isis.core.runtimeservices.icons;
 
 import java.net.URL;
 import java.util.Map;
@@ -38,6 +38,8 @@ import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.commons.internal.collections._Maps;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
 import org.apache.isis.commons.internal.resources._Resources;
+import org.apache.isis.core.metamodel.facets.object.icon.ObjectIcon;
+import org.apache.isis.core.metamodel.facets.object.icon.ObjectIconService;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 
 import lombok.NonNull;
@@ -46,8 +48,8 @@ import lombok.SneakyThrows;
 import lombok.val;
 
 @Service
-@Named("isis.viewer.common.ObjectIconServiceDefault")
-@Priority(PriorityPrecedence.LATE)
+@Named("isis.runtimeservices.ObjectIconServiceDefault")
+@Priority(PriorityPrecedence.MIDPOINT)
 @Qualifier("Default")
 @RequiredArgsConstructor(onConstructor_ = {@Inject})
 public class ObjectIconServiceDefault
@@ -67,7 +69,7 @@ implements ObjectIconService {
     private final Map<String, ObjectIcon> iconByKey = _Maps.newConcurrentHashMap();
 
     private final ObjectIcon fallbackIcon =
-            new ObjectIcon(
+            ObjectIcon.eager(
                     "ObjectIconFallback",
                     _Resources.getResourceUrl(
                             ObjectIconServiceDefault.class,
@@ -85,7 +87,7 @@ implements ObjectIconService {
                 ? domainClass.getName() + "-" + iconNameModifier
                 : domainClass.getName();
 
-        // also memoize unsuccessful icon lookups, so we don't search repeatedly
+        // also memoize unsuccessful icon lookups (as fallback), so we don't search repeatedly
 
         val cachedIcon = iconByKey.get(iconResourceKey);
         if(cachedIcon!=null) {
@@ -103,8 +105,8 @@ implements ObjectIconService {
         //     findIcon(spec, iconNameModifier));
     }
 
-    @Override
-    public ObjectIcon getObjectFallbackIcon() {
+    //@Override
+    private ObjectIcon getObjectFallbackIcon() {
         return fallbackIcon;
     }
 
@@ -129,7 +131,7 @@ implements ObjectIconService {
                 .map(suffix->iconResourceNameNoExt + "." + suffix)
                 .map(iconResourceName->
                         classPathResource(domainClass, iconResourceName)
-                        .map(url->new ObjectIcon(
+                        .map(url->ObjectIcon.lazy(
                                 iconResourceNameNoExt,
                                 url,
                                 imageType)))
@@ -153,7 +155,7 @@ implements ObjectIconService {
                     .map(suffix->DEFAULT_IMAGE_RESOURCE_PATH + "/" + iconResourceNameNoExt + "." + suffix)
                     .map(iconResourcePath->
                             classPathResource(iconResourcePath)
-                            .map(url->new ObjectIcon(
+                            .map(url->ObjectIcon.lazy(
                                     iconResourceNameNoExt,
                                     url,
                                     imageType)))
diff --git a/viewers/common/src/main/resources/org/apache/isis/viewer/common/model/decorator/icon/ObjectIconFallback.png b/core/runtimeservices/src/main/resources/org/apache/isis/core/runtimeservices/icons/ObjectIconFallback.png
similarity index 100%
rename from viewers/common/src/main/resources/org/apache/isis/viewer/common/model/decorator/icon/ObjectIconFallback.png
rename to core/runtimeservices/src/main/resources/org/apache/isis/core/runtimeservices/icons/ObjectIconFallback.png
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/IsisModuleViewerCommon.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/IsisModuleViewerCommon.java
index a7ed0d6..507a7e1 100644
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/IsisModuleViewerCommon.java
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/IsisModuleViewerCommon.java
@@ -25,7 +25,6 @@ import org.apache.isis.viewer.common.applib.mixins.Object_impersonate;
 import org.apache.isis.viewer.common.applib.mixins.Object_impersonateWithRoles;
 import org.apache.isis.viewer.common.applib.mixins.Object_stopImpersonating;
 import org.apache.isis.viewer.common.model.branding.BrandingUiModelProvider;
-import org.apache.isis.viewer.common.model.decorator.icon.ObjectIconServiceDefault;
 import org.apache.isis.viewer.common.model.header.HeaderUiModelProvider;
 import org.apache.isis.viewer.common.model.menu.MenuUiModelProvider;
 import org.apache.isis.viewer.common.model.userprofile.UserProfileUiModelProviderDefault;
@@ -38,7 +37,6 @@ import org.apache.isis.viewer.common.model.userprofile.UserProfileUiModelProvide
     UserProfileUiModelProviderDefault.class,
     MenuUiModelProvider.class,
     HeaderUiModelProvider.class,
-    ObjectIconServiceDefault.class,
 
     // @Mixin's
     Object_impersonate.class,
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/decorator/icon/ObjectIcon.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/decorator/icon/ObjectIcon.java
deleted file mode 100644
index 9408030..0000000
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/decorator/icon/ObjectIcon.java
+++ /dev/null
@@ -1,53 +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.viewer.common.model.decorator.icon;
-
-import java.io.InputStream;
-import java.io.Serializable;
-import java.net.URL;
-
-import org.apache.isis.applib.value.NamedWithMimeType.CommonMimeType;
-import org.apache.isis.commons.internal._Constants;
-import org.apache.isis.commons.internal.base._Bytes;
-import org.apache.isis.commons.internal.base._Strings;
-
-import lombok.Getter;
-
-@lombok.Value
-public class ObjectIcon
-implements Serializable {
-
-    private static final long serialVersionUID = 1L;
-
-    private String shortName;
-    private URL url;
-    private CommonMimeType mimeType;
-
-    @Getter(lazy = true)
-    private String identifier = _Strings.base64UrlEncode(getUrl().getPath());
-
-    public byte[] asBytes() {
-        try(final InputStream is = url.openStream()){
-            return _Bytes.of(is);
-        } catch (Exception e) {
-            return _Constants.emptyBytes;
-        }
-    }
-
-}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/decorator/icon/ObjectIconService.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/decorator/icon/ObjectIconService.java
deleted file mode 100644
index bd97e62..0000000
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/decorator/icon/ObjectIconService.java
+++ /dev/null
@@ -1,51 +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.viewer.common.model.decorator.icon;
-
-import javax.annotation.Nullable;
-
-import org.apache.isis.core.metamodel.spec.ManagedObject;
-import org.apache.isis.core.metamodel.spec.ManagedObjects;
-import org.apache.isis.core.metamodel.spec.ObjectSpecification;
-
-import lombok.NonNull;
-
-public interface ObjectIconService {
-
-    ObjectIcon getObjectIcon(
-            @NonNull ObjectSpecification specification,
-            @Nullable String iconNameIfAny);
-
-    ObjectIcon getObjectFallbackIcon();
-
-    // -- SHORTCUTS
-
-    default ObjectIcon getObjectIcon(final ObjectSpecification spec) {
-        return getObjectIcon(spec, null);
-    }
-
-    default ObjectIcon getObjectIcon(final ManagedObject adapter) {
-        return ManagedObjects.isSpecified(adapter)
-                ? getObjectIcon(
-                        adapter.getSpecification(),
-                        adapter.getSpecification().getIconName(adapter))
-                : getObjectFallbackIcon();
-    }
-
-}
diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java
index f4ca6f7..364e45e 100644
--- a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java
+++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java
@@ -51,13 +51,12 @@ import org.apache.isis.core.metamodel.consent.Consent;
 import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.facets.object.grid.GridFacet;
+import org.apache.isis.core.metamodel.facets.object.icon.ObjectIcon;
 import org.apache.isis.core.metamodel.interactions.managed.ManagedMember;
 import org.apache.isis.core.metamodel.interactions.managed.MemberInteraction.AccessIntent;
 import org.apache.isis.core.metamodel.interactions.managed.PropertyInteraction;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ManagedObjects.EntityUtil;
-import org.apache.isis.viewer.common.model.decorator.icon.ObjectIcon;
-import org.apache.isis.viewer.common.model.decorator.icon.ObjectIconService;
 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.Rel;
 import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
@@ -79,16 +78,12 @@ import lombok.extern.log4j.Log4j2;
 @Path("/objects") @Log4j2
 public class DomainObjectResourceServerside extends ResourceAbstract implements DomainObjectResource {
 
-    private final ObjectIconService objectIconService;
-
     @Inject
     public DomainObjectResourceServerside(
             final MetaModelContext metaModelContext,
             final IsisConfiguration isisConfiguration,
-            final ObjectIconService objectIconService,
             final InteractionLayerTracker iInteractionLayerTracker) {
         super(metaModelContext, isisConfiguration, iInteractionLayerTracker);
-        this.objectIconService = objectIconService;
         log.debug("<init>");
     }
 
@@ -248,7 +243,7 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
 //                RepresentationType.OBJECT_ICON, Where.ANYWHERE, RepresentationService.Intent.NOT_APPLICABLE);
 
         val objectAdapter = getObjectAdapterElseThrowNotFound(domainType, instanceId);
-        val objectIcon = objectIconService.getObjectIcon(objectAdapter);
+        val objectIcon = objectAdapter.getIcon();
 
         return Response.ok(
                 objectIcon.asBytes(),
diff --git a/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/services/ImageResourceCacheClassPath.java b/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/services/ImageResourceCacheClassPath.java
index 04108cf..48d16e3 100644
--- a/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/services/ImageResourceCacheClassPath.java
+++ b/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/services/ImageResourceCacheClassPath.java
@@ -21,7 +21,6 @@ package org.apache.isis.viewer.wicket.viewer.services;
 import java.io.ByteArrayInputStream;
 
 import javax.annotation.Priority;
-import javax.inject.Inject;
 import javax.inject.Named;
 
 import org.apache.wicket.Application;
@@ -34,10 +33,9 @@ import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
 import org.apache.isis.applib.annotation.PriorityPrecedence;
+import org.apache.isis.core.metamodel.facets.object.icon.ObjectIcon;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
-import org.apache.isis.viewer.common.model.decorator.icon.ObjectIcon;
-import org.apache.isis.viewer.common.model.decorator.icon.ObjectIconService;
 import org.apache.isis.viewer.wicket.model.models.ImageResourceCache;
 
 import lombok.NonNull;
@@ -55,23 +53,21 @@ import lombok.val;
 @Named("isis.viewer.wicket.ImageResourceCacheClassPath")
 @Priority(PriorityPrecedence.MIDPOINT)
 @Qualifier("ClassPath")
-@RequiredArgsConstructor(onConstructor_ = {@Inject})
+//@RequiredArgsConstructor(onConstructor_ = {@Inject})
 public class ImageResourceCacheClassPath
 implements ImageResourceCache {
 
     private static final long serialVersionUID = 1L;
 
-    private final ObjectIconService objectIconService;
-
     @Override
     public ResourceReference resourceReferenceFor(final ManagedObject adapter) {
-        val objectIcon = objectIconService.getObjectIcon(adapter);
+        val objectIcon = adapter.getIcon();
         return resourceReferenceForObjectIcon(objectIcon);
     }
 
     @Override
     public ResourceReference resourceReferenceForSpec(final ObjectSpecification spec) {
-        val objectIcon = objectIconService.getObjectIcon(spec);
+        val objectIcon = spec.getIcon(null);
         return resourceReferenceForObjectIcon(objectIcon);
     }