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 2019/03/06 17:01:17 UTC

[isis] 01/01: ISIS-2102: Support for Server Side Events (SSE)

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

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

commit 26cd14fd2c7439523b18d241205b268c987d020e
Author: Andi Huber <ah...@apache.org>
AuthorDate: Wed Mar 6 18:01:04 2019 +0100

    ISIS-2102: Support for Server Side Events (SSE)
    
    extending the programming model with @Property(observe=...)
    
    Task-Url: https://issues.apache.org/jira/browse/ISIS-2102
---
 .../apache/isis/applib/annotation/Property.java    | 11 ++++
 .../isis/applib/events/sse/EventStream.java}       | 34 ++++++----
 .../isis/applib/events/sse/EventStreamSource.java  | 70 ++++++++++++++++++++
 .../org/apache/isis/applib/util/JaxbAdapters.java  | 34 ++++++++++
 .../facets/objectvalue/observe/ObserveFacet.java}  | 19 +++---
 .../objectvalue/observe/ObserveFacetAbstract.java  | 61 ++++++++++++++++++
 .../property/PropertyAnnotationFacetFactory.java   | 20 +++++-
 .../observe/ObserveFacetForPropertyAnnotation.java | 54 ++++++++++++++++
 core/pom.xml                                       |  2 +-
 .../background/BackgroundServiceDefault.java       |  5 +-
 .../components/scalars/markup/MarkupComponent.java | 24 +++++--
 .../scalars/markup/MarkupComponent_observing.java  | 74 ++++++++++++++++++++++
 .../ui/components/scalars/markup/MarkupPanel.java  | 27 ++++++--
 .../scalars/markup/ObservingComponent.js           | 23 +++++++
 .../scalars/markup/StandaloneMarkupPanel.java      |  2 +-
 .../wicket/ui/errors/ExceptionStackTracePanel.java |  2 +-
 16 files changed, 422 insertions(+), 40 deletions(-)

diff --git a/core/applib/src/main/java/org/apache/isis/applib/annotation/Property.java b/core/applib/src/main/java/org/apache/isis/applib/annotation/Property.java
index 51591a7..7ff3e08 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/annotation/Property.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/annotation/Property.java
@@ -30,6 +30,7 @@ import javax.jdo.annotations.NotPersistent;
 import org.apache.isis.applib.conmap.ContentMappingServiceForCommandDto;
 import org.apache.isis.applib.conmap.ContentMappingServiceForCommandsDto;
 import org.apache.isis.applib.events.domain.PropertyDomainEvent;
+import org.apache.isis.applib.events.sse.EventStreamSource;
 import org.apache.isis.applib.services.command.CommandDtoProcessor;
 import org.apache.isis.applib.services.command.CommandWithDto;
 import org.apache.isis.applib.services.command.spi.CommandService;
@@ -253,5 +254,15 @@ public @interface Property {
      * @see <a href="http://www.w3schools.com/tags/att_input_accept.asp">http://www.w3schools.com</a>
      */
     String fileAccept() default "";
+    
+    
+    
+    
+    
+    /**
+     * TODO
+     *
+     */
+    Class<? extends EventStreamSource> observe() default EventStreamSource.Nop.class;
 
 }
\ No newline at end of file
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/StandaloneMarkupPanel.java b/core/applib/src/main/java/org/apache/isis/applib/events/sse/EventStream.java
similarity index 59%
copy from core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/StandaloneMarkupPanel.java
copy to core/applib/src/main/java/org/apache/isis/applib/events/sse/EventStream.java
index 60d665d..59be17a 100644
--- a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/StandaloneMarkupPanel.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/events/sse/EventStream.java
@@ -17,19 +17,31 @@
  *  under the License.
  */
 
-package org.apache.isis.viewer.wicket.ui.components.scalars.markup;
+package org.apache.isis.applib.events.sse;
 
-import org.apache.isis.viewer.wicket.model.models.ValueModel;
-import org.apache.isis.viewer.wicket.ui.panels.PanelAbstract;
+import java.util.UUID;
+import java.util.function.Predicate;
 
-public class StandaloneMarkupPanel extends PanelAbstract<ValueModel> {
-
-    private static final long serialVersionUID = 1L;
-    private static final String ID_STANDALONE_VALUE = "standaloneValue";
+/**
+ * Server-side events.
+ *  
+ * @see https://www.w3schools.com/html/html5_serversentevents.asp
+ * 
+ * @since 2.0.0-M3
+ *
+ */
+public interface EventStream {
 
-    public StandaloneMarkupPanel(final String id, final ValueModel valueModel) {
-        super(id, valueModel);
-        add(new MarkupComponent(ID_STANDALONE_VALUE, getModel()));
-    }
+    UUID getId();
+    Class<?> getSourceType();
 
+    void listenWhile(Predicate<EventStreamSource> listener);
+    
+    void fire(EventStreamSource source);
+    
+    void close();
+    
+    void awaitClose() throws InterruptedException;
+    
+    
 }
diff --git a/core/applib/src/main/java/org/apache/isis/applib/events/sse/EventStreamSource.java b/core/applib/src/main/java/org/apache/isis/applib/events/sse/EventStreamSource.java
new file mode 100644
index 0000000..df58373
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/events/sse/EventStreamSource.java
@@ -0,0 +1,70 @@
+/*
+ *  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.applib.events.sse;
+
+import org.apache.isis.applib.value.Markup;
+
+/**
+ * Server-side events.
+ *  
+ * @see https://www.w3schools.com/html/html5_serversentevents.asp
+ * 
+ * @since 2.0.0-M3
+ *
+ */
+public interface EventStreamSource {
+
+    void run(EventStream eventStream);
+    
+    Markup getPayload();
+    
+    // -- PROPERTY ANNOTATION DEFAULT
+    
+    /**
+     * This class is the default for the
+     * {@link org.apache.isis.applib.annotation.Property#observe()} annotation attribute.  
+     */
+    public static final class Nop implements EventStreamSource {
+
+        @Override
+        public void run(EventStream eventStream) {
+            // just do nothing
+        }
+
+        @Override
+        public Markup getPayload() {
+            return new Markup();
+        }
+        
+    }
+    
+    // -- BASIC PREDICATES
+    
+    public static boolean isObservable(Class<?> type) {
+        if(type==null) {
+            return false;
+        }
+        if(!EventStreamSource.class.isAssignableFrom(type)) {
+            return false;    
+        }
+        return !type.equals(Nop.class);
+    }
+    
+}
diff --git a/core/applib/src/main/java/org/apache/isis/applib/util/JaxbAdapters.java b/core/applib/src/main/java/org/apache/isis/applib/util/JaxbAdapters.java
index ace6f9c..8a81955 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/util/JaxbAdapters.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/util/JaxbAdapters.java
@@ -18,12 +18,17 @@
  */
 package org.apache.isis.applib.util;
 
+import java.nio.charset.StandardCharsets;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.OffsetDateTime;
+import java.util.Base64;
 
 import javax.xml.bind.annotation.adapters.XmlAdapter;
 
+import org.apache.isis.applib.value.Markup;
+import org.apache.isis.commons.internal.base._Strings;
+
 /**
  * Provides JAXB XmlAdapters for Java built-in temporal types. 
  * Others types might be added, if convenient. 
@@ -39,6 +44,35 @@ import javax.xml.bind.annotation.adapters.XmlAdapter;
  */
 public final class JaxbAdapters {
 
+    // -- MARKUP
+    
+    public static final class MarkupAdapter extends XmlAdapter<String, Markup>{
+        
+        private final static Base64.Encoder encoder = Base64.getEncoder(); 
+        private final static Base64.Decoder decoder = Base64.getDecoder();
+
+        @Override
+        public Markup unmarshal(String v) throws Exception {
+            if(v==null) {
+                return null;
+            }
+            final String html = _Strings.ofBytes(decoder.decode(v), StandardCharsets.UTF_8);
+            return new Markup(html);
+        }
+
+        @Override
+        public String marshal(Markup v) throws Exception {
+            if(v==null) {
+                return null;
+            }
+            final String html = v.asString();
+            return encoder.encodeToString(_Strings.toBytes(html, StandardCharsets.UTF_8));
+        }
+
+    }
+    
+    // -- TEMPORAL VALUE TYPES
+    
     public static final class DateAdapter extends XmlAdapter<String, java.util.Date>{
 
         public java.util.Date unmarshal(String v) throws Exception {
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/StandaloneMarkupPanel.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/observe/ObserveFacet.java
similarity index 59%
copy from core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/StandaloneMarkupPanel.java
copy to core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/observe/ObserveFacet.java
index 60d665d..078bcb1 100644
--- a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/StandaloneMarkupPanel.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/observe/ObserveFacet.java
@@ -17,19 +17,16 @@
  *  under the License.
  */
 
-package org.apache.isis.viewer.wicket.ui.components.scalars.markup;
+package org.apache.isis.core.metamodel.facets.objectvalue.observe;
 
-import org.apache.isis.viewer.wicket.model.models.ValueModel;
-import org.apache.isis.viewer.wicket.ui.panels.PanelAbstract;
+import org.apache.isis.applib.events.sse.EventStreamSource;
+import org.apache.isis.core.metamodel.facets.SingleClassValueFacet;
 
-public class StandaloneMarkupPanel extends PanelAbstract<ValueModel> {
-
-    private static final long serialVersionUID = 1L;
-    private static final String ID_STANDALONE_VALUE = "standaloneValue";
+/**
+ * Corresponds to <tt>@Property(observe=...)</tt> annotation in the Isis programming model.
+ */
+public interface ObserveFacet extends SingleClassValueFacet {
 
-    public StandaloneMarkupPanel(final String id, final ValueModel valueModel) {
-        super(id, valueModel);
-        add(new MarkupComponent(ID_STANDALONE_VALUE, getModel()));
-    }
+    Class<? extends EventStreamSource> getEventStreamType();
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/observe/ObserveFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/observe/ObserveFacetAbstract.java
new file mode 100644
index 0000000..2762f0f
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/observe/ObserveFacetAbstract.java
@@ -0,0 +1,61 @@
+/*
+ *  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.objectvalue.observe;
+
+import org.apache.isis.applib.events.sse.EventStreamSource;
+import org.apache.isis.commons.internal.exceptions._Exceptions;
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.SingleClassValueFacetAbstract;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+
+public abstract class ObserveFacetAbstract extends SingleClassValueFacetAbstract implements ObserveFacet {
+
+    private Class<? extends EventStreamSource> eventStreamType;
+
+    public static Class<? extends Facet> type() {
+        return ObserveFacet.class;
+    }
+
+    public ObserveFacetAbstract(
+            final Class<? extends EventStreamSource> eventStreamType,
+            final FacetHolder holder) {
+
+        super(type(), holder, eventStreamType, null);
+        this.eventStreamType = eventStreamType;
+    }
+
+    @Override
+    public Class<?> value() {
+        return eventStreamType;
+    }
+
+    @Override
+    public Class<? extends EventStreamSource> getEventStreamType() {
+        return eventStreamType;
+    }
+    
+    @Override
+    public ObjectSpecification valueSpec() {
+        throw _Exceptions.notImplemented();
+    }
+
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
index 4e40191..898de3c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
@@ -43,6 +43,7 @@ import org.apache.isis.core.metamodel.facets.object.domainobject.domainevents.Pr
 import org.apache.isis.core.metamodel.facets.objectvalue.fileaccept.FileAcceptFacet;
 import org.apache.isis.core.metamodel.facets.objectvalue.mandatory.MandatoryFacet;
 import org.apache.isis.core.metamodel.facets.objectvalue.maxlen.MaxLengthFacet;
+import org.apache.isis.core.metamodel.facets.objectvalue.observe.ObserveFacet;
 import org.apache.isis.core.metamodel.facets.objectvalue.regex.RegExFacet;
 import org.apache.isis.core.metamodel.facets.propcoll.accessor.PropertyOrCollectionAccessorFacet;
 import org.apache.isis.core.metamodel.facets.propcoll.notpersisted.NotPersistedFacet;
@@ -64,6 +65,7 @@ import org.apache.isis.core.metamodel.facets.properties.property.modify.Property
 import org.apache.isis.core.metamodel.facets.properties.property.modify.PropertySetterFacetForDomainEventFromPropertyAnnotation;
 import org.apache.isis.core.metamodel.facets.properties.property.mustsatisfy.MustSatisfySpecificationFacetForPropertyAnnotation;
 import org.apache.isis.core.metamodel.facets.properties.property.notpersisted.NotPersistedFacetForPropertyAnnotation;
+import org.apache.isis.core.metamodel.facets.properties.property.observe.ObserveFacetForPropertyAnnotation;
 import org.apache.isis.core.metamodel.facets.properties.property.publishing.PublishedPropertyFacetForPropertyAnnotation;
 import org.apache.isis.core.metamodel.facets.properties.property.regex.RegExFacetForPatternAnnotationOnProperty;
 import org.apache.isis.core.metamodel.facets.properties.property.regex.RegExFacetForPropertyAnnotation;
@@ -75,9 +77,11 @@ import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorCom
 import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorForConflictingOptionality;
 import org.apache.isis.core.metamodel.util.EventUtil;
 
-public class PropertyAnnotationFacetFactory extends FacetFactoryAbstract implements MetaModelValidatorRefiner {
+public class PropertyAnnotationFacetFactory extends FacetFactoryAbstract 
+implements MetaModelValidatorRefiner {
 
-    private final MetaModelValidatorForConflictingOptionality conflictingOptionalityValidator = new MetaModelValidatorForConflictingOptionality();
+    private final MetaModelValidatorForConflictingOptionality conflictingOptionalityValidator = 
+            new MetaModelValidatorForConflictingOptionality();
 
 
     public PropertyAnnotationFacetFactory() {
@@ -98,6 +102,7 @@ public class PropertyAnnotationFacetFactory extends FacetFactoryAbstract impleme
         processOptional(processMethodContext);
         processRegEx(processMethodContext);
         processFileAccept(processMethodContext);
+        processObserve(processMethodContext);
     }
 
 
@@ -370,6 +375,17 @@ public class PropertyAnnotationFacetFactory extends FacetFactoryAbstract impleme
 
         FacetUtil.addFacet(facet);
     }
+    
+    void processObserve(final ProcessMethodContext processMethodContext) {
+        final Method method = processMethodContext.getMethod();
+        final FacetHolder holder = processMethodContext.getFacetHolder();
+
+        // else search for @Property(observe=...)
+        final List<Property> properties = Annotations.getAnnotations(method, Property.class);
+        ObserveFacet facet = ObserveFacetForPropertyAnnotation.create(properties, holder);
+
+        FacetUtil.addFacet(facet);
+    }
 
 
     // //////////////////////////////////////
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/observe/ObserveFacetForPropertyAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/observe/ObserveFacetForPropertyAnnotation.java
new file mode 100644
index 0000000..c23a4af
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/observe/ObserveFacetForPropertyAnnotation.java
@@ -0,0 +1,54 @@
+/*
+ *  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.properties.property.observe;
+
+import java.util.List;
+
+import org.apache.isis.applib.annotation.Property;
+import org.apache.isis.applib.events.sse.EventStreamSource;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.objectvalue.observe.ObserveFacet;
+import org.apache.isis.core.metamodel.facets.objectvalue.observe.ObserveFacetAbstract;
+
+public class ObserveFacetForPropertyAnnotation extends ObserveFacetAbstract {
+
+    public static ObserveFacet create(
+            final List<Property> properties,
+            final FacetHolder holder) {
+
+        return properties.stream()
+                .map(Property::observe)
+                .peek(x->System.out.println("..."+x))
+                .filter(EventStreamSource::isObservable)
+                .findFirst()
+                .map(eventStreamType -> new ObserveFacetForPropertyAnnotation(
+                        eventStreamType, holder))
+                .orElse(null);
+    }
+
+    private ObserveFacetForPropertyAnnotation(
+            Class<? extends EventStreamSource> eventStreamType, 
+            FacetHolder holder) {
+        
+        super(eventStreamType, holder);
+    }
+    
+
+}
diff --git a/core/pom.xml b/core/pom.xml
index 702ac6b..efa89ac 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -174,7 +174,7 @@
         
         <picocontainer.version>2.15</picocontainer.version>
         
-        <javaee.version>7.0</javaee.version>
+        <javaee.version>8.0</javaee.version>
 
         <htmlparser.version>2.1</htmlparser.version>
 
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundServiceDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundServiceDefault.java
index ed6d905..ad885fd 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundServiceDefault.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundServiceDefault.java
@@ -16,10 +16,7 @@
  */
 package org.apache.isis.core.runtime.services.background;
 
-import static org.apache.isis.commons.internal.base._Casts.uncheckedCast;
-
 import java.lang.reflect.InvocationHandler;
-import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
@@ -46,6 +43,8 @@ import org.apache.isis.core.metamodel.specloader.classsubstitutor.ProxyEnhanced;
 import org.apache.isis.core.plugins.codegen.ProxyFactory;
 import org.apache.isis.core.runtime.system.session.IsisSessionFactory;
 
+import static org.apache.isis.commons.internal.base._Casts.uncheckedCast;
+
 /**
  * For command-reification depends on an implementation of
  * {@link org.apache.isis.applib.services.background.BackgroundCommandService} to
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/MarkupComponent.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/MarkupComponent.java
index f3b4bfa..b8ae03c 100644
--- a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/MarkupComponent.java
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/MarkupComponent.java
@@ -19,25 +19,39 @@
 
 package org.apache.isis.viewer.wicket.ui.components.scalars.markup;
 
-import org.apache.isis.applib.value.Markup;
-import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.wicket.markup.ComponentTag;
 import org.apache.wicket.markup.MarkupStream;
 import org.apache.wicket.markup.html.WebComponent;
 import org.apache.wicket.markup.parser.XmlTag.TagType;
 import org.apache.wicket.model.IModel;
 
+import org.apache.isis.applib.value.LocalResourcePath;
+import org.apache.isis.applib.value.Markup;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+
 public class MarkupComponent extends WebComponent {
 
     private static final long serialVersionUID = 1L;
+    
+    private final LocalResourcePath observing;
 
-    public MarkupComponent(final String id, IModel<?> model){
+    public MarkupComponent(final String id, IModel<?> model, LocalResourcePath observing){
         super(id, model);
+        this.observing = observing;
     }
 
     @Override
     public void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag){
-        replaceComponentTagBody(markupStream, openTag, extractHtmlOrElse(getDefaultModelObject(), ""));
+        final CharSequence htmlContent = extractHtmlOrElse(getDefaultModelObject(), "" /*fallback*/);
+        replaceComponentTagBody(
+                markupStream, 
+                openTag, 
+                
+                observing!=null 
+                    ? MarkupComponent_observing.decorate(htmlContent, observing)
+                            : htmlContent
+                
+                );
     }
 
     @Override
@@ -72,4 +86,6 @@ public class MarkupComponent extends WebComponent {
         return modelObject.toString();
 
     }
+
+    
 }
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/MarkupComponent_observing.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/MarkupComponent_observing.java
new file mode 100644
index 0000000..773de2d
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/MarkupComponent_observing.java
@@ -0,0 +1,74 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.wicket.ui.components.scalars.markup;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.UUID;
+
+import org.apache.isis.applib.value.LocalResourcePath;
+import org.apache.isis.commons.internal.resources._Resources;
+
+import static org.apache.isis.commons.internal.base._Strings.isNullOrEmpty;
+
+final class MarkupComponent_observing  {
+
+    private static final String jScriptTemplateResource = "ObservingComponent.js";
+    
+    static CharSequence decorate(CharSequence htmlContent, LocalResourcePath observing) {
+        if(observing==null) {
+            return htmlContent;
+        }
+        final String jScriptTemplate;
+        try {
+            jScriptTemplate = _Resources.loadAsString(
+                    MarkupComponent_observing.class, jScriptTemplateResource, StandardCharsets.UTF_8);
+            
+        } catch (IOException e) {
+            e.printStackTrace();
+            return resourceNotFound();
+        }
+        
+        if(isNullOrEmpty(jScriptTemplate)) {
+            return resourceNotFound();
+        }
+        
+        final String targetId = UUID.randomUUID().toString();
+        final String observingPath = _Resources.prependContextPathIfPresent(observing.getPath());
+        
+        final StringBuilder sb = new StringBuilder();
+        sb
+        .append("<div id=\"").append(targetId).append("\">\n")
+        .append(htmlContent)
+        .append("\n</div>\n")
+        .append("<script type=\"text/javascript\">\n")
+        .append(jScriptTemplate
+                .replace("${targetId}", targetId)
+                .replace("${observing}", observingPath))
+        .append("\n</script>\n");
+        
+        return sb.toString();
+    }
+
+    private static String resourceNotFound() {
+        return "Template resource not found: '"+jScriptTemplateResource+"'.";
+    }
+    
+}
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/MarkupPanel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/MarkupPanel.java
index f6e9ffd..133b345 100644
--- a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/MarkupPanel.java
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/MarkupPanel.java
@@ -19,14 +19,17 @@
 
 package org.apache.isis.viewer.wicket.ui.components.scalars.markup;
 
-import org.apache.isis.viewer.wicket.model.models.ScalarModel;
-import org.apache.isis.viewer.wicket.ui.components.scalars.ScalarPanelTextFieldParseableAbstract;
-import org.apache.isis.viewer.wicket.ui.components.widgets.bootstrap.FormGroup;
 import org.apache.wicket.Component;
 import org.apache.wicket.MarkupContainer;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.model.Model;
 
+import org.apache.isis.applib.value.LocalResourcePath;
+import org.apache.isis.core.metamodel.facets.objectvalue.observe.ObserveFacet;
+import org.apache.isis.viewer.wicket.model.models.ScalarModel;
+import org.apache.isis.viewer.wicket.ui.components.scalars.ScalarPanelTextFieldParseableAbstract;
+import org.apache.isis.viewer.wicket.ui.components.widgets.bootstrap.FormGroup;
+
 /**
  * Panel for rendering scalars of type {@link org.apache.isis.applib.value.Markup}.
  */
@@ -50,8 +53,9 @@ public class MarkupPanel extends ScalarPanelTextFieldParseableAbstract {
             // fallback to text editor
             return super.createScalarIfRegularFormGroup();
         }
-
-        final MarkupComponent markupComponent = createMarkupComponent("scalarValueContainer");
+        
+        final MarkupComponent markupComponent = 
+                createMarkupComponent("scalarValueContainer");
 
         getTextField().setLabel(Model.of(getModel().getName()));
 
@@ -74,9 +78,20 @@ public class MarkupPanel extends ScalarPanelTextFieldParseableAbstract {
     // -- HELPER
 
     private MarkupComponent createMarkupComponent(String id) {
-        MarkupComponent markupComponent = new MarkupComponent(id, getModel());
+        MarkupComponent markupComponent = new MarkupComponent(id, getModel(), getEventStreamResource());
         markupComponent.setEnabled(false);
         return markupComponent;
     }
+    
+    private LocalResourcePath getEventStreamResource() {
+        final ObserveFacet observeFacet  = super.scalarModel.getFacet(ObserveFacet.class);
+        if(observeFacet==null) {
+            return null;
+        }
+        final String eventStreamId = observeFacet.getEventStreamType().getName();
+        final LocalResourcePath ssePath = new LocalResourcePath("/sse?eventStream=" + eventStreamId);
+        return ssePath;
+    }
+    
 
 }
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/ObservingComponent.js b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/ObservingComponent.js
new file mode 100644
index 0000000..f46b00b
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/ObservingComponent.js
@@ -0,0 +1,23 @@
+var sse_observe = function(targetId, observing) { 
+
+	function updateField(newValue) {
+		document.getElementById(targetId).innerHTML = newValue;  
+	}
+	
+	function isEventSourceSupported() {
+		return typeof(EventSource) !== "undefined";  
+	}
+	
+	if(isEventSourceSupported()) {
+	  var source = new EventSource(observing);
+	  source.onmessage = function(event) {
+		    var decodedData = window.atob(event.data);
+		    updateField(decodedData);
+	  };
+	} else {
+		updateField("Sorry, your browser does not support server-sent events.");
+	}
+
+}
+
+sse_observe("${targetId}", "${observing}");
\ No newline at end of file
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/StandaloneMarkupPanel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/StandaloneMarkupPanel.java
index 60d665d..913df13 100644
--- a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/StandaloneMarkupPanel.java
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/StandaloneMarkupPanel.java
@@ -29,7 +29,7 @@ public class StandaloneMarkupPanel extends PanelAbstract<ValueModel> {
 
     public StandaloneMarkupPanel(final String id, final ValueModel valueModel) {
         super(id, valueModel);
-        add(new MarkupComponent(ID_STANDALONE_VALUE, getModel()));
+        add(new MarkupComponent(ID_STANDALONE_VALUE, getModel(), null /*observing*/));
     }
 
 }
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.java
index c77f601..2d07567 100644
--- a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.java
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.java
@@ -98,7 +98,7 @@ public class ExceptionStackTracePanel extends Panel {
                 if(ticketMarkup == null) {
                     Components.permanentlyHide(this, ID_TICKET_MARKUP);
                 } else {
-                    add(new MarkupComponent(ID_TICKET_MARKUP, Model.of(ticket.getMarkup())));
+                    add(new MarkupComponent(ID_TICKET_MARKUP, Model.of(ticket.getMarkup()), null /*observing*/));
                 }
 
                 final boolean suppressExceptionDetail =