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