Add the webapp archetype
Define a complete and functional WebApplication skeleton in an maven archetype project
update other achetypes dependencies to use the 4.0.0 versionned maven-bundle-plugin
diff --git a/distribution/archetypes/pom.xml b/distribution/archetypes/pom.xml
index f1b2c3d..024b297 100644
--- a/distribution/archetypes/pom.xml
+++ b/distribution/archetypes/pom.xml
@@ -30,5 +30,6 @@
<modules>
<module>sensinact-basis-archetype</module>
<module>sensinact-http-archetype</module>
+ <module>sensinact-webapp-archetype</module>
</modules>
</project>
diff --git a/distribution/archetypes/sensinact-basis-archetype/src/main/resources/archetype-resources/pom.xml b/distribution/archetypes/sensinact-basis-archetype/src/main/resources/archetype-resources/pom.xml
index 506382c..48f5d6c 100644
--- a/distribution/archetypes/sensinact-basis-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/distribution/archetypes/sensinact-basis-archetype/src/main/resources/archetype-resources/pom.xml
@@ -17,7 +17,7 @@
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
- <version>2.5.3</version>
+ <version>4.0.0</version>
<extensions>true</extensions>
<configuration>
<unpackBundle>false</unpackBundle>
diff --git a/distribution/archetypes/sensinact-http-archetype/src/main/resources/archetype-resources/pom.xml b/distribution/archetypes/sensinact-http-archetype/src/main/resources/archetype-resources/pom.xml
index f866e2d..017122f 100644
--- a/distribution/archetypes/sensinact-http-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/distribution/archetypes/sensinact-http-archetype/src/main/resources/archetype-resources/pom.xml
@@ -17,7 +17,7 @@
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
- <version>2.5.3</version>
+ <version>4.0.0</version>
<extensions>true</extensions>
<configuration>
<unpackBundle>false</unpackBundle>
diff --git a/distribution/archetypes/sensinact-webapp-archetype/pom.xml b/distribution/archetypes/sensinact-webapp-archetype/pom.xml
new file mode 100644
index 0000000..ddfb7f5
--- /dev/null
+++ b/distribution/archetypes/sensinact-webapp-archetype/pom.xml
@@ -0,0 +1,17 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.eclipse.sensinact.gateway.archetypes</groupId>
+ <artifactId>parent</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <groupId>org.eclipse.sensinact.gateway.archetypes</groupId>
+ <artifactId>sensinact-webapp-archetype</artifactId>
+ <version>1.0</version>
+ <packaging>jar</packaging>
+
+</project>
\ No newline at end of file
diff --git a/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
new file mode 100644
index 0000000..71cb0d8
--- /dev/null
+++ b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<archetype-descriptor name="http">
+ <fileSets>
+ <fileSet filtered="true" packaged="true">
+ <directory>src/main/java</directory>
+ <includes>
+ <include>**/*.*</include>
+ </includes>
+ </fileSet>
+ <fileSet filtered="true" packaged="false">
+ <directory>src/main/resources/</directory>
+ <includes>
+ <include>**/*</include>
+ </includes>
+ </fileSet>
+ </fileSets>
+ <requiredProperties>
+ <requiredProperty key="groupId">
+ <defaultValue>org.eclipse.sensinact.gateway.sample</defaultValue>
+ </requiredProperty>
+ <requiredProperty key="artifactId"/>
+ <requiredProperty key="version">
+ <defaultValue>1.0</defaultValue>
+ </requiredProperty>
+ <requiredProperty key="sensinactVersion"/>
+ <requiredProperty key="package">
+ <defaultValue>org.eclipse.sensinact.gateway.sample.web</defaultValue>
+ </requiredProperty>
+ </requiredProperties>
+</archetype-descriptor>
\ No newline at end of file
diff --git a/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/pom.xml b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/pom.xml
new file mode 100644
index 0000000..e494de6
--- /dev/null
+++ b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/pom.xml
@@ -0,0 +1,154 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>${groupId}</groupId>
+ <artifactId>${artifactId}</artifactId>
+ <version>${version}</version>
+
+ <packaging>bundle</packaging>
+
+ <properties>
+ <maven.build.timestamp.format>yyyy.mm.dd</maven.build.timestamp.format>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>4.0.0</version>
+ <extensions>true</extensions>
+ <configuration>
+ <unpackBundle>false</unpackBundle>
+ <instructions>
+ <Bundle-SymbolicName>${artifactId}</Bundle-SymbolicName>
+ <Bundle-Name>${project.name}</Bundle-Name>
+ <Bundle-Description>${project.name}</Bundle-Description>
+ <_nouses>true</_nouses>
+ <_nodefaultversion>true</_nodefaultversion>
+ <_exportcontents>!${package}.internal</_exportcontents>
+ <Import-Package>
+ org.eclipse.sensinact.gateway.core,
+ org.eclipse.sensinact.gateway.core.message,
+ org.eclipse.sensinact.gateway.core.message.annotation,
+ org.eclipse.sensinact.gateway.core.message.whiteboard,
+ org.eclipse.sensinact.gateway.core.method,
+ org.eclipse.sensinact.gateway.core.method.builder,
+ org.eclipse.sensinact.gateway.core.method.trigger,
+ org.eclipse.sensinact.gateway.core.method.legacy,
+ org.eclipse.sensinact.gateway.core.remote,
+ org.eclipse.sensinact.gateway.core.security,
+ org.eclipse.sensinact.gateway.generic,
+ org.eclipse.sensinact.gateway.generic.annotation,
+ org.eclipse.sensinact.gateway.generic.local,
+ org.eclipse.sensinact.gateway.generic.packet,
+ org.eclipse.sensinact.gateway.generic.packet.annotation,
+ org.eclipse.sensinact.gateway.generic.parser,
+ org.eclipse.sensinact.gateway.generic.stream,
+ org.eclipse.sensinact.gateway.common.annotation,
+ org.eclipse.sensinact.gateway.common.bundle,
+ org.eclipse.sensinact.gateway.common.constraint,
+ org.eclipse.sensinact.gateway.common.execution,
+ org.eclipse.sensinact.gateway.common.interpolator,
+ org.eclipse.sensinact.gateway.common.interpolator.exception,
+ org.eclipse.sensinact.gateway.common.primitive,
+ org.eclipse.sensinact.gateway.util,
+ org.eclipse.sensinact.gateway.util.crypto,
+ org.eclipse.sensinact.gateway.util.json,
+ org.eclipse.sensinact.gateway.util.location,
+ org.eclipse.sensinact.gateway.util.rest,
+ org.eclipse.sensinact.gateway.util.stack,
+ org.eclipse.sensinact.gateway.util.tree,
+ org.eclipse.sensinact.gateway.util.xml,
+ org.eclipse.sensinact.gateway.protocol.http,
+ org.eclipse.sensinact.gateway.protocol.http.*,
+ org.eclipse.sensinact.gateway.sthbnd.http,
+ org.eclipse.sensinact.gateway.sthbnd.http.*,
+ *
+ </Import-Package>
+ <Bundle-Activator>${package}.osgi.Activator</Bundle-Activator>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>6.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.cmpn</artifactId>
+ <version>7.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>1.7.30</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.sensinact.gateway</groupId>
+ <artifactId>sensinact-utils</artifactId>
+ <version>${sensinactVersion}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.sensinact.gateway</groupId>
+ <artifactId>sensinact-common</artifactId>
+ <version>${sensinactVersion}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.sensinact.gateway</groupId>
+ <artifactId>sensinact-core</artifactId>
+ <version>${sensinactVersion}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.sensinact.gateway</groupId>
+ <artifactId>sensinact-generic</artifactId>
+ <version>${sensinactVersion}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.sensinact.gateway.protocol</groupId>
+ <artifactId>http</artifactId>
+ <version>${sensinactVersion}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.sensinact.gateway.nthbnd</groupId>
+ <artifactId>http-tools</artifactId>
+ <version>${sensinactVersion}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.http.servlet-api</artifactId>
+ <version>1.1.2</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.http.jetty</artifactId>
+ <version>4.0.14</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/WebAppConstants.java b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/WebAppConstants.java
new file mode 100644
index 0000000..5457583
--- /dev/null
+++ b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/WebAppConstants.java
@@ -0,0 +1,14 @@
+package ${package};
+
+/**
+ * Web application constants bucket
+ */
+public abstract class WebAppConstants {
+
+ public static final String WEBAPP_ROOT = "/webapp";
+
+ public static final String WEBAPP_ALIAS = "/webapp/*";
+
+ public static final String WEBAPP_CALLBACK = "/webapp.callback";
+
+}
diff --git a/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/component/AgentRelayImpl.java b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/component/AgentRelayImpl.java
new file mode 100644
index 0000000..8bdc63c
--- /dev/null
+++ b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/component/AgentRelayImpl.java
@@ -0,0 +1,29 @@
+package ${package}.app.component;
+
+import org.eclipse.sensinact.gateway.core.message.AgentRelay;
+import org.eclipse.sensinact.gateway.core.message.MidCallbackException;
+import org.eclipse.sensinact.gateway.core.message.SnaMessage;
+import org.eclipse.sensinact.gateway.core.message.SnaUpdateMessageImpl;
+import org.eclipse.sensinact.gateway.core.message.whiteboard.AbstractAgentRelay;
+import org.eclipse.sensinact.gateway.core.message.annotation.Filter;
+
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import ${package}.app.service.WebAppRelay;
+
+/**
+ * {@link AgentRelay} implementation
+ */
+@Filter(handled = {SnaMessage.Type.UPDATE})
+@Component(immediate=true, service=AgentRelay.class)
+public class AgentRelayImpl extends AbstractAgentRelay {
+
+ @Reference
+ private WebAppRelay webapp;
+
+ @Override
+ public void doHandle(SnaUpdateMessageImpl message) throws MidCallbackException {
+ webapp.relay(message);
+ }
+}
diff --git a/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/component/CallbackHandler.java b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/component/CallbackHandler.java
new file mode 100644
index 0000000..d9a8471
--- /dev/null
+++ b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/component/CallbackHandler.java
@@ -0,0 +1,80 @@
+package ${package}.app.component;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.io.IOException;
+
+import org.eclipse.sensinact.gateway.common.bundle.Mediator;
+import org.eclipse.sensinact.gateway.core.message.SnaMessage;
+import org.eclipse.sensinact.gateway.nthbnd.http.callback.CallbackContext;
+import org.eclipse.sensinact.gateway.nthbnd.http.callback.ResponseWrapper;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.json.JSONObject;
+import org.json.JSONException;
+
+/**
+ * Handles calls coming from a distinct remote endpoint
+ */
+public class CallbackHandler {
+
+ private static final Logger LOG = LoggerFactory.getLogger(CallbackHandler.class);
+
+ private ResponseWrapper response;
+ private boolean locked;
+
+ /**
+ * Constructor
+ */
+ public CallbackHandler() {
+ this.locked = false;
+ }
+
+ /**
+ * Processes the request wrapped by the {@link CallbackContext} passed
+ * as parameter, to send back the response that is also wrapped by the
+ * {@link CallbackContext} argument
+ *
+ * @param context the {@link CallbackContext} wrapping the request to be
+ * processed and the response to be sent back to the requirer
+ */
+ public void process(CallbackContext context) {
+ this.response = context.getResponse();
+ String content = context.getRequest().getContent();
+ if(content != null) {
+ try {
+ JSONObject obj = new JSONObject(content);
+ this.locked = obj.optBoolean("locked");
+ } catch(NullPointerException | JSONException e) {
+ LOG.error(e.getMessage(),e);
+ }
+ }
+ }
+
+ /**
+ * Transmits the message passed as parameter to the connected remote endpoint
+ *
+ * @param message {@link SnaMessage} to transmit
+ */
+ public void doRelay(SnaMessage<?> message) {
+ if(locked)
+ return;
+ try {
+ response.setContent(message.getJSON().getBytes());
+ response.flush();
+ } catch (Exception e) {
+ LOG.error(e.getMessage(),e);
+ try {
+ response.setContent(String.format("Internal server error :%s", e.getMessage()).getBytes());
+ response.setResponseStatus(520);
+ response.flush();
+ } catch(Exception ex) {
+ LOG.error(ex.getMessage(),ex);
+ }
+ }
+ }
+}
diff --git a/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/component/CallbackServiceImpl.java b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/component/CallbackServiceImpl.java
new file mode 100644
index 0000000..e2b825b
--- /dev/null
+++ b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/component/CallbackServiceImpl.java
@@ -0,0 +1,85 @@
+package ${package}.app.component;
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.sensinact.gateway.core.message.SnaMessage;
+import org.eclipse.sensinact.gateway.common.bundle.Mediator;
+import org.eclipse.sensinact.gateway.nthbnd.http.callback.CallbackContext;
+import org.eclipse.sensinact.gateway.nthbnd.http.callback.CallbackService;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import ${package}.app.service.WebAppRelay;
+import ${package}.WebAppConstants;
+
+import org.osgi.service.component.annotations.Component;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link CallbackService} implementation
+ */
+@Component(immediate=true, service={CallbackService.class, WebAppRelay.class})
+public class CallbackServiceImpl implements CallbackService, WebAppRelay {
+
+ private static final Logger LOG = LoggerFactory.getLogger(CallbackServiceImpl.class);
+
+ public static final String UUID_KEY="uuid";
+
+ private Map<String,CallbackHandler> handlers ;
+
+ /**
+ * Constructor
+ */
+ public CallbackServiceImpl() {
+ this.handlers = new HashMap<String,CallbackHandler>();
+ }
+
+ @Override
+ public String getPattern() {
+ return WebAppConstants.WEBAPP_CALLBACK;
+ }
+
+ @Override
+ public int getCallbackType() {
+ return CallbackService.CALLBACK_WEBSOCKET;
+ }
+
+ @Override
+ public Dictionary getProperties() {
+ return new Hashtable() {{
+ this.put("pattern", WebAppConstants.WEBAPP_CALLBACK);
+ }};
+ }
+
+ @Override
+ public void process(CallbackContext context) {
+ Map<String, List<String>> attributes = context.getRequest().getAttributes();
+ List<String> uuids = attributes.get(UUID_KEY);
+ String uuid = (uuids==null || uuids.size()==0)?null:uuids.get(0);
+ if(uuid == null) {
+ new CallbackHandler().process(context);
+ return;
+ }
+ CallbackHandler handler = handlers.get(uuid);
+ if(handler == null) {
+ handler = new CallbackHandler();
+ handlers.put(uuid,handler);
+ }
+ handler.process(context);
+ }
+
+ @Override
+ public void relay(SnaMessage<?> message) {
+ if(handlers.isEmpty())
+ return;
+ handlers.values().parallelStream().forEach(h -> h.doRelay(message));
+ }
+}
diff --git a/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/service/WebAppRelay.java b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/service/WebAppRelay.java
new file mode 100644
index 0000000..cb481b9
--- /dev/null
+++ b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/service/WebAppRelay.java
@@ -0,0 +1,16 @@
+package ${package}.app.service;
+
+import org.eclipse.sensinact.gateway.core.message.SnaMessage;
+
+/**
+ * A WebAppRelay is in charge of relaying received {@link SnaMessage}
+ */
+public interface WebAppRelay {
+
+ /**
+ * Relays the received {@link SnaMessage}
+ *
+ * @param message the {@link SnaMessage} to be relayed
+ */
+ void relay(SnaMessage<?> message);
+}
diff --git a/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/servlet/IndexFilter.java b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/servlet/IndexFilter.java
new file mode 100644
index 0000000..fa7f249
--- /dev/null
+++ b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/servlet/IndexFilter.java
@@ -0,0 +1,31 @@
+package ${package}.app.servlet;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.annotation.WebFilter;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import ${package}.WebAppConstants;
+
+/**
+ * Redirect to index.html page
+ */
+@WebFilter( urlPatterns= {WebAppConstants.WEBAPP_ROOT}, asyncSupported=false)
+public class IndexFilter implements Filter {
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {}
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ ((HttpServletResponse) response).sendRedirect(WebAppConstants.WEBAPP_ROOT + "/index.html");
+ }
+
+ @Override
+ public void destroy() {}
+}
diff --git a/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/servlet/MirrorServlet.java b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/servlet/MirrorServlet.java
new file mode 100644
index 0000000..432707f
--- /dev/null
+++ b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/servlet/MirrorServlet.java
@@ -0,0 +1,39 @@
+package ${package}.app.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Mirror servlet
+ * Registered only to avoid 404 not found error - but never effectively called
+ */
+public class MirrorServlet extends HttpServlet implements Servlet {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MirrorServlet.class);
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ try {
+ super.init(config);
+ }catch(Exception e) {
+ LOG.error(e.getMessage(),e);
+ throw new ServletException(e);
+ }
+ }
+
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ response.setStatus(200);
+ response.flushBuffer();
+ }
+
+}
diff --git a/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/servlet/ResourceFilter.java b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/servlet/ResourceFilter.java
new file mode 100644
index 0000000..dd1bfdf
--- /dev/null
+++ b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/app/servlet/ResourceFilter.java
@@ -0,0 +1,162 @@
+package ${package}.app.servlet;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.GZIPOutputStream;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.annotation.WebFilter;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.sensinact.gateway.common.bundle.Mediator;
+import org.eclipse.sensinact.gateway.util.IOUtils;
+import org.osgi.framework.Bundle;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ${package}.WebAppConstants;
+
+/**
+ * Serves the web resources
+ */
+@WebFilter( urlPatterns= {WebAppConstants.WEBAPP_ALIAS}, asyncSupported=false)
+public class ResourceFilter implements Filter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ResourceFilter.class);
+
+ @SuppressWarnings("serial")
+ private static final Map<String,String> MIME = new HashMap<String,String>() {{
+ put(".aac","audio/aac");
+ put(".abw","application/x-abiword");
+ put(".arc","application/octet-stream");
+ put(".avi","video/x-msvideo");
+ put(".azw","application/vnd.amazon.ebook");
+ put(".bin","application/octet-stream");
+ put(".bz","application/x-bzip");
+ put(".bz2","application/x-bzip2");
+ put(".csh","application/x-csh");
+ put(".css","text/css");
+ put(".csv","text/csv");
+ put(".doc","application/msword");
+ put(".docx","application/vnd.openxmlformats-officedocument.wordprocessingml.document");
+ put(".eot","application/vnd.ms-fontobject");
+ put(".epub","application/epub+zip");
+ put(".gif","image/gif");
+ put(".htm","text/html");
+ put(".html","text/html");
+ put(".ico" ,"image/x-icon");
+ put(".ics","text/calendar");
+ put(".jar","application/java-archive");
+ put(".jpeg","image/jpeg");
+ put(".jpg","image/jpeg");
+ put(".js","application/javascript");
+ put(".json","application/json");
+ put(".mid","audio/midi");
+ put(".midi","audio/midi");
+ put(".mpeg","video/mpeg");
+ put(".mpkg","application/vnd.apple.installer+xml");
+ put(".odp","application/vnd.oasis.opendocument.presentation");
+ put(".ods","application/vnd.oasis.opendocument.spreadsheet");
+ put(".odt","application/vnd.oasis.opendocument.text");
+ put(".oga","audio/ogg");
+ put(".ogv","video/ogg");
+ put(".ogx","application/ogg");
+ put(".otf","font/otf");
+ put(".png","image/png");
+ put(".pdf","application/pdf");
+ put(".ppt","application/vnd.ms-powerpoint");
+ put(".pptx","application/vnd.openxmlformats-officedocument.presentationml.presentation");
+ put(".rar","application/x-rar-compressed");
+ put(".rtf","application/rtf");
+ put(".sh","application/x-sh");
+ put(".svg","image/svg+xml");
+ put(".swf","application/x-shockwave-flash");
+ put(".tar","application/x-tar");
+ put(".tif","image/tiff");
+ put(".tiff","image/tiff");
+ put(".ts","application/typescript");
+ put(".ttf","font/ttf");
+ put(".vsd","application/vnd.visio");
+ put(".wav","audio/x-wav");
+ put(".weba","audio/webm");
+ put(".webm","video/webm");
+ put(".webp","image/webp");
+ put(".woff","font/woff");
+ put(".woff2","font/woff2");
+ put(".xhtml","application/xhtml+xml");
+ put(".xls","application/vnd.ms-excel");
+ put(".xlsx","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+ put(".xml","application/xml");
+ put(".xul","application/vnd.mozilla.xul+xml");
+ put(".zip","application/zip");
+ put(".7z","application/x-7z-compressed");
+ }};
+
+ private Bundle bundle;
+
+ /**
+ * Constructor
+ *
+ * @param mediator the {@link Mediator} allowing the ResourceFilter to be
+ * instantiated to interact with the OSGi host environment
+ */
+ public ResourceFilter(Mediator mediator) {
+ this.bundle = mediator.getContext().getBundle();
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {}
+
+ @Override
+ public void doFilter(ServletRequest req, ServletResponse res, final FilterChain chain) throws IOException, ServletException {
+ if (res.isCommitted()) {
+ return;
+ }
+ String target = ((HttpServletRequest)req).getRequestURI();
+ if (target == null) {
+ target = "";
+ }
+ if (!target.startsWith("/")) {
+ target = "/" + target;
+ }
+ String resName = target;
+
+ int index = resName.lastIndexOf('.');
+ String contentType = MIME.get(resName.substring(index<0?0:index));
+
+ final URL url;
+ synchronized(ResourceFilter.this.bundle) {
+ url = ResourceFilter.this.bundle.getEntry(resName);
+ }
+
+ try {
+ if(url != null) {
+ if(contentType != null) {
+ ((HttpServletResponse)res).setHeader("Content-Type", contentType);
+ }
+ final ServletOutputStream output = res.getOutputStream();
+ byte[] resourceBytes = IOUtils.read(url.openStream());
+ output.write(resourceBytes);
+ ((HttpServletResponse)res).setStatus(HttpServletResponse.SC_OK);
+ } else {
+ ((HttpServletResponse)res).setStatus(HttpServletResponse.SC_NOT_FOUND);
+ }
+ } catch (Exception e) {
+ LOG.error(e.getMessage(),e);
+ }
+ }
+
+ @Override
+ public void destroy() {}
+}
diff --git a/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/osgi/Activator.java b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/osgi/Activator.java
new file mode 100644
index 0000000..0f1e948
--- /dev/null
+++ b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/java/osgi/Activator.java
@@ -0,0 +1,62 @@
+package ${package}.osgi;
+
+import java.util.Hashtable;
+
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
+
+import org.eclipse.sensinact.gateway.common.bundle.AbstractActivator;
+import org.eclipse.sensinact.gateway.common.bundle.Mediator;
+
+import org.eclipse.sensinact.gateway.nthbnd.http.callback.CallbackService;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
+
+import ${package}.app.component.CallbackServiceImpl;
+import ${package}.app.servlet.IndexFilter;
+import ${package}.app.servlet.ResourceFilter;
+import ${package}.app.servlet.MirrorServlet;
+import ${package}.WebAppConstants;
+
+/**
+ * Handle the bundle activation / deactivation
+ */
+public class Activator extends AbstractActivator<Mediator> {
+
+ @Override
+ public void doStart() {
+ mediator.register(new Hashtable() {{
+ this.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_FILTER_PATTERN, WebAppConstants.WEBAPP_ROOT);
+ this.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,"("+HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME+"=default)");
+ }
+ },new IndexFilter(),
+ new Class<?>[] {Filter.class});
+ super.mediator.info(String.format("%s filter registered", WebAppConstants.WEBAPP_ROOT));
+
+ mediator.register(new Hashtable() {{
+ this.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_FILTER_PATTERN, WebAppConstants.WEBAPP_ALIAS);
+ this.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,"("+HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME+"=default)");
+ }
+ }, new ResourceFilter(super.mediator),
+ new Class<?>[] {Filter.class});
+ super.mediator.info(String.format("%s filter registered", WebAppConstants.WEBAPP_ALIAS));
+
+ mediator.register(new Hashtable() {{
+ this.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, WebAppConstants.WEBAPP_ALIAS);
+ this.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,"("+HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME+"=default)");
+ }
+ }, new MirrorServlet(),
+ new Class<?>[] {Servlet.class});
+ }
+
+ @Override
+ public void doStop() {
+ mediator.info("Swagger API was unregistered from %s context", WebAppConstants.WEBAPP_ALIAS);
+ }
+
+ @Override
+ public Mediator doInstantiate(BundleContext context) {
+ return new Mediator(context);
+ }
+
+}
diff --git a/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/resources/webapp/index.html b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/resources/webapp/index.html
new file mode 100644
index 0000000..b8bd90b
--- /dev/null
+++ b/distribution/archetypes/sensinact-webapp-archetype/src/main/resources/archetype-resources/src/main/resources/webapp/index.html
@@ -0,0 +1,133 @@
+<html >
+<head>
+<title>sensiNact WebApp</title>
+<style>
+ * {
+ box-sizing: border-box;
+ }
+
+ textarea {
+ width: 100%;
+ height: 300px;
+ padding: 12px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ resize: vertical;
+ }
+
+ input[type=button] {
+ background-color: #4CAF50;
+ color: white;
+ padding: 12px 20px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ float: right;
+ }
+
+ input[type=button]:hover {
+ background-color: #45a049;
+ }
+
+ .col-50 {
+ float: left;
+ width: 40%;
+ margin-top: 6px;
+ }
+
+ .col-100 {
+ float: left;
+ width: 90%;
+ margin-top: 6px;
+ }
+
+ .row:after {
+ content: "";
+ display: table;
+ clear: both;
+ }
+
+ @media screen and (max-width: 600px) {
+ .col-20, .col-100, input[type=button] {
+ width: 100%;
+ margin-top: 0;
+ }
+ }
+</style>
+</head>
+<body>
+ <h3>sensiNact Sample WebApp</h3>
+ <div class="row">
+ <div class="col-100">
+ <textarea id="eventsFrame"></textarea>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-50">
+ <input type="button" id="fstart" value="Reset">
+ </div>
+ <div class="col-50">
+ <input type="button" id="fstop" value="Pause">
+ </div>
+ </div>
+ </div>
+</body>
+<script>
+
+function uuidv4() {
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
+ return v.toString(16);
+ });
+};
+
+let uuid = uuidv4();
+
+var txtArea = document.getElementById("eventsFrame") ;
+
+//send a JSON object
+function send(json) {
+ json["uuid"] = uuid;
+ socket.send(JSON.stringify(json));
+};
+
+//change to the effective sensiNact instance IP
+let socket = new WebSocket("ws://localhost:8080/ws/webapp.callback");
+
+socket.onopen = function(e) {
+ alert("[open] Connection established");
+ let json = {};
+ json["locked"]=false;
+ send(json);
+};
+
+socket.onmessage = function(event) {
+ txtArea.value += event.data + '\r\n';
+};
+
+socket.onclose = function(event) {
+ if (event.wasClean) {
+ alert(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
+ } else {
+ alert('[close] Connection died');
+ }
+};
+
+socket.onerror = function(error) {
+ alert(`[error] ${error.message}`);
+};
+
+var start = document.getElementById("fstart");
+start.onclick = function(e){
+ let json = {};
+ json["locked"]=false;
+ send(json);
+}
+
+var stop = document.getElementById("fstop");
+stop.onclick = function(e){
+ let json = {};
+ json["locked"]=true;
+ send(json);
+}
+</script>
\ No newline at end of file