diff --git a/docker/Dockerfile b/docker/Dockerfile
index 4259dcf87ead2ef2104dc508a6281bd158cf8370..00efdade78173a9e86a0116e86db20cc2a8f62e4 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -20,5 +20,5 @@ COPY elasticsearch.yml /elasticsearch/config/elasticsearch.yml
 COPY nginx.conf /etc/nginx/nginx.conf
 COPY webapps /tomcat/webapps/
 COPY webroot /webroot/
-EXPOSE 80 9300 27017
+EXPOSE 80 6789 9300 27017
 CMD ["/usr/bin/supervisord"]
\ No newline at end of file
diff --git a/docker/docker-run.sh b/docker/docker-run.sh
index c77b960862a3bdfd76a88f7f1062811b3535c0a2..cc06ef3567f07b0569ac3297bb86f303d2435546 100755
--- a/docker/docker-run.sh
+++ b/docker/docker-run.sh
@@ -5,6 +5,7 @@
 
 # set host machine ports
 HOST_NGINX=80
+HOST_IPC=6789
 HOST_ELASTICSEARCH_API=9300
 HOST_MONGODB=27017
 
@@ -15,7 +16,7 @@ REPLACE_WEBROOT=1
 #######################################################################################
 
 DIR="$(dirname "$(readlink -f "$0")")"
-PORT_MAPPING="-p $HOST_NGINX:80 -p $HOST_ELASTICSEARCH_API:9300 -p $HOST_MONGODB:27017"
+PORT_MAPPING="-p $HOST_NGINX:80 -p $HOST_IPC:6789 -p $HOST_ELASTICSEARCH_API:9300 -p $HOST_MONGODB:27017"
 
 if [ $REPLACE_WEBAPPS -eq 1 ]; then
 	VOLUME_WEBAPPS="-v $DIR/webapps:/tomcat/webapps"
diff --git a/docker/nginx.conf b/docker/nginx.conf
index 9cae4d2869893a1b36e390256cc1196036b6d329..a76a421d0feb0fd4d9f90fd28af39a72d5726bf2 100644
--- a/docker/nginx.conf
+++ b/docker/nginx.conf
@@ -20,13 +20,14 @@ http {
   error_log /var/log/nginx/error.log;
 
   gzip on;
+  gzip_static on;
   gzip_disable "msie6";
   gzip_vary on;
   gzip_proxied any;
   gzip_comp_level 6;
   gzip_buffers 16 8k;
   gzip_http_version 1.1;
-  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
+  gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/rss+xml text/javascript image/svg+xml application/vnd.ms-fontobject application/x-font-ttf font/opentype;
 
   server {
     listen 80 default;
@@ -42,7 +43,18 @@ http {
       proxy_buffering off;
       proxy_set_header Host $host;
       proxy_set_header X-Real-IP $remote_addr;
+      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       proxy_pass http://localhost:8080/rest/;
     }
+
+    location /ws {
+      proxy_http_version 1.1;
+      proxy_set_header Upgrade $http_upgrade;
+      proxy_set_header Connection "upgrade";
+      proxy_set_header Host $host;
+      proxy_set_header X-Real-IP $remote_addr;
+      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+      proxy_pass http://localhost:8080/ws;
+    }
   }
 }
\ No newline at end of file
diff --git a/make-release.sh b/make-release.sh
index 2d85f1f62fd75a31c8bcd8ffac6857d4b6fc9ea6..bd344450faa2d8d8a939ab384df8f57e1f5fa948 100755
--- a/make-release.sh
+++ b/make-release.sh
@@ -14,7 +14,7 @@ DIR="$(dirname "$(readlink -f "$0")")"
 REL_NAME="vipra-$VERSION"
 REL="$DIR/$REL_NAME"
 
-rm -rf $REL $REL.tar.gz
+rm -rf $REL vipra*.tar.gz
 mkdir -p $REL/vipra-backend $REL/vipra-cmd $REL/vipra-ui
 cp -r $DIR/dtm_release $REL/dtm_release
 find $REL -type f -name "*.o" -exec rm -f {} \;
@@ -24,6 +24,7 @@ cp $DIR/vipra-cmd/target/*.jar $REL/vipra-cmd/vipra.jar
 cp -r $DIR/vipra-cmd/target/lib $REL/vipra-cmd/lib
 cp -r $DIR/vipra-ui/public $REL/vipra-ui
 cp $DIR/vipra $REL/vipra-cmd/vipra
+cp $DIR/vipra $REL/vipra
 cp $DIR/LICENSE $DIR/README.md $REL
 cp $DIR/scripts/* $REL
 
diff --git a/scripts/install.sh b/scripts/install.sh
old mode 100644
new mode 100755
index cc1f786e84631faabc68d86a3aefffbd1ae03a06..092a48ac3fcae74db4ddab447814853cbb42e2be
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -1 +1,16 @@
-#!/bin/bash
\ No newline at end of file
+#!/bin/bash
+
+DIR="$(dirname "$(readlink -f "$0")")"
+
+cd $DIR
+
+sudo rm -rf /opt/vipra
+sudo mkdir -p /opt/vipra
+sudo cp -r ./* /opt/vipra/
+sudo ln -sf /opt/vipra/vipra /bin/vipra
+
+cd $OLDPWD
+
+echo "vipra installed to /opt/vipra"
+echo "use 'vipra' command to access vipra, test system with 'vipra -t'"
+echo "REQUIRED: install libgsl-dev and manually compile dtm: 'sudo make -C /opt/vipra/dtm_release/dtm'"
\ No newline at end of file
diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh
old mode 100644
new mode 100755
index cc1f786e84631faabc68d86a3aefffbd1ae03a06..50c945636aa263558aef8a41bc30c060bc9dd739
--- a/scripts/uninstall.sh
+++ b/scripts/uninstall.sh
@@ -1 +1,6 @@
-#!/bin/bash
\ No newline at end of file
+#!/bin/bash
+
+sudo rm -rf /opt/vipra
+sudo rm /bin/vipra
+rm -rf $HOME/.config/vipra
+rm -rf $HOME/.local/share/vipra
\ No newline at end of file
diff --git a/vipra b/vipra
index eb76199dd4bdbd26fb745d844eb3c1a1524e1613..f41c26c7ddbaa43841379c36841608374fbbefd9 100755
--- a/vipra
+++ b/vipra
@@ -10,11 +10,16 @@ if [ $? -ne 0 ]; then
 fi
 
 JARS="$VIPRA_JAR
-/opt/vipra/vipra-cmd/target/vipra*.jar
-vipra*.jar
+$VIPRA_HOME/vipra-cmd/vipra*.jar
+$VIPRA_HOME/vipra-cmd/target/vipra*.jar
+$VIPRA_HOME/target/vipra*.jar
+$VIPRA_HOME/vipra*.jar
+$DIR/vipra-cmd/vipra*.jar
 $DIR/vipra-cmd/target/vipra*.jar
 $DIR/target/vipra*.jar
-$DIR/vipra*.jar"
+$DIR/vipra*.jar
+/opt/vipra/vipra-cmd/target/vipra*.jar
+vipra*.jar"
 for f in $JARS
 do
   if [ -f $f ]; then
diff --git a/vipra-backend/src/main/java/de/vipra/rest/Application.java b/vipra-backend/src/main/java/de/vipra/rest/Application.java
index eb1e7649e367c8dbe4ae8a98f5fce85c8f7f20ae..1af81e485178f0114eb72dbb2ffc095f63479a0a 100644
--- a/vipra-backend/src/main/java/de/vipra/rest/Application.java
+++ b/vipra-backend/src/main/java/de/vipra/rest/Application.java
@@ -11,6 +11,7 @@ import de.vipra.rest.provider.ObjectMapperProvider;
 public class Application extends ResourceConfig {
 
 	public Application() {
+		// register rest application
 		packages("de.vipra.rest.resource");
 		register(JacksonFeature.class);
 		register(CORSResponseFilter.class);
diff --git a/vipra-backend/src/main/java/de/vipra/rest/provider/RestServletContextListener.java b/vipra-backend/src/main/java/de/vipra/rest/provider/RestServletContextListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..9f932b398e728f9c6e912c5fbbd109b0ecbf4be0
--- /dev/null
+++ b/vipra-backend/src/main/java/de/vipra/rest/provider/RestServletContextListener.java
@@ -0,0 +1,27 @@
+package de.vipra.rest.provider;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import de.vipra.util.IPCServer;
+import de.vipra.ws.WebSocketChain;
+
+public class RestServletContextListener implements ServletContextListener {
+
+	public static final Logger log = LogManager.getLogger(RestServletContextListener.class);
+
+	@Override
+	public void contextDestroyed(final ServletContextEvent arg0) {}
+
+	@Override
+	public void contextInitialized(final ServletContextEvent arg0) {
+		final IPCServer ipcServer = IPCServer.getInstance();
+		ipcServer.register("websocket", new WebSocketChain());
+		ipcServer.start();
+		log.info("started ipc server");
+	}
+
+}
diff --git a/vipra-backend/src/main/java/de/vipra/ws/WebSocket.java b/vipra-backend/src/main/java/de/vipra/ws/WebSocket.java
new file mode 100644
index 0000000000000000000000000000000000000000..7e31d080e228911d0fb368d58f95b1a84a0db255
--- /dev/null
+++ b/vipra-backend/src/main/java/de/vipra/ws/WebSocket.java
@@ -0,0 +1,61 @@
+package de.vipra.ws;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+
+import de.vipra.util.Config;
+import de.vipra.util.IPCMessage;
+
+@ServerEndpoint("/ws")
+public class WebSocket {
+
+	public static final Logger log = LogManager.getLogger(WebSocket.class);
+
+	private static final Set<Session> sessions = new HashSet<>();
+
+	@OnOpen
+	public void open(final Session session) {
+		sessions.add(session);
+	}
+
+	@OnClose
+	public void close(final Session session) {
+		sessions.remove(session);
+	}
+
+	@OnError
+	public void onError(final Throwable error) {
+		log.error(error);
+	}
+
+	@OnMessage
+	public void handleMessage(final String input, final Session session) throws JsonParseException, JsonMappingException, IOException {}
+
+	public static void sendMessage(final IPCMessage message) throws JsonProcessingException {
+		sendMessage(Config.mapper.writeValueAsString(message));
+	}
+
+	public static void sendMessage(final String message) {
+		for (final Session session : sessions)
+			if (session.isOpen())
+				session.getAsyncRemote().sendText(message);
+			else
+				sessions.remove(session);
+	}
+
+}
diff --git a/vipra-backend/src/main/java/de/vipra/ws/WebSocketChain.java b/vipra-backend/src/main/java/de/vipra/ws/WebSocketChain.java
new file mode 100644
index 0000000000000000000000000000000000000000..a8efcaf286a84c4f56da3154852663f6ec3fa14e
--- /dev/null
+++ b/vipra-backend/src/main/java/de/vipra/ws/WebSocketChain.java
@@ -0,0 +1,31 @@
+package de.vipra.ws;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+import de.vipra.util.IPCChain;
+import de.vipra.util.IPCMessage;
+
+public class WebSocketChain implements IPCChain {
+
+	public static final Logger log = LogManager.getLogger(WebSocketChain.class);
+
+	@Override
+	public void call(final IPCMessage message) {
+		switch (message.getCode()) {
+			case MESSAGE:
+				WebSocket.sendMessage(message.getMessage());
+				break;
+			default:
+				try {
+					WebSocket.sendMessage(message);
+				} catch (final JsonProcessingException e) {
+					log.error(e);
+				}
+				break;
+		}
+	}
+
+}
diff --git a/vipra-backend/src/main/resources/config.json b/vipra-backend/src/main/resources/config.json
index b5a9ad2919414e3006df5a8acbbfeeeee475bf8b..dbea69e69826ba341db53d9cf1fa7fc15522c0ec 100644
--- a/vipra-backend/src/main/resources/config.json
+++ b/vipra-backend/src/main/resources/config.json
@@ -3,5 +3,7 @@
 	"databasePort": 27017,
 	"databaseName": "test",
 	"elasticSearchHost": "127.0.0.1",
-	"elasticSearchPort": 9300
+	"elasticSearchPort": 9300,
+	"ipcHost": "127.0.0.1",
+	"ipcPort": 6789
 }
\ No newline at end of file
diff --git a/vipra-backend/src/main/webapp/WEB-INF/web.xml b/vipra-backend/src/main/webapp/WEB-INF/web.xml
index da89b35de944060a099ff17a206f8e391abc82c3..889bf7934570d306415b8a1d100d0ecbd0d755e6 100644
--- a/vipra-backend/src/main/webapp/WEB-INF/web.xml
+++ b/vipra-backend/src/main/webapp/WEB-INF/web.xml
@@ -15,4 +15,9 @@
 		<servlet-name>jersey</servlet-name>
 		<url-pattern>/rest/*</url-pattern>
 	</servlet-mapping>
+	<listener>
+	    <listener-class>
+	        de.vipra.rest.provider.RestServletContextListener
+	    </listener-class>
+	</listener>
 </web-app>
\ No newline at end of file
diff --git a/vipra-cmd/runcfg/CMD.launch b/vipra-cmd/runcfg/CMD.launch
index 3f9ca734b340ba4c09c275021439c0ee8e184b7c..b36db2e1f99f6ae77ceb1a8501b802a2a37c5e24 100644
--- a/vipra-cmd/runcfg/CMD.launch
+++ b/vipra-cmd/runcfg/CMD.launch
@@ -11,7 +11,7 @@
 </listAttribute>
 <stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.m2e.launchconfig.classpathProvider"/>
 <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="de.vipra.cmd.Main"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-D test"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-t"/>
 <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="vipra-cmd"/>
 <stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.m2e.launchconfig.sourcepathProvider"/>
 <stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea"/>
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/CommandLineOptions.java b/vipra-cmd/src/main/java/de/vipra/cmd/CommandLineOptions.java
index 4ce7824862b41f916a378c2c92f9b642ddf621f1..45e3b4adc920a1cb54094c2f882fbe0ae19275ec 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/CommandLineOptions.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/CommandLineOptions.java
@@ -37,6 +37,7 @@ public class CommandLineOptions {
 	public static final Option MODEL = Option.builder("M").longOpt("model").desc("generate topics on models").hasArgs().argName("[models...]")
 			.optionalArg(true).build();
 	public static final Option SELECT = Option.builder("S").longOpt("select").desc("select models").hasArgs().argName("models...").build();
+	public static final Option MESSAGE = Option.builder("m").longOpt("message").desc("send a global message").hasArg().argName("message").build();
 
 	private final Options options;
 	private CommandLine cmd;
@@ -44,7 +45,7 @@ public class CommandLineOptions {
 
 	public CommandLineOptions() {
 		final Option[] optionsArray = { CLEAR, DEBUG, HELP, INDEX, LIST, REREAD, SILENT, TEST, ALL, CREATE, DELETE, EDIT, PRINT, IMPORT, MODEL,
-				SELECT };
+				SELECT, MESSAGE };
 		options = new Options();
 		for (final Option option : optionsArray)
 			options.addOption(option);
@@ -56,9 +57,9 @@ public class CommandLineOptions {
 
 	private String[] split(final String[] args) {
 		final List<String> args2 = new ArrayList<>();
-		for (String arg : args) {
+		for (final String arg : args) {
 			if (arg.startsWith("-") && arg.length() > 2) {
-				for (char c : arg.substring(1, arg.length()).toCharArray())
+				for (final char c : arg.substring(1, arg.length()).toCharArray())
 					args2.add("-" + c);
 			} else {
 				args2.add(arg);
@@ -217,4 +218,12 @@ public class CommandLineOptions {
 		throw new RuntimeException("select at least one model");
 	}
 
+	public boolean isMessage() {
+		return hasOption(MESSAGE);
+	}
+
+	public String messageToSend() {
+		return getOptionValue(MESSAGE);
+	}
+
 }
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/ErrorCodes.java b/vipra-cmd/src/main/java/de/vipra/cmd/ErrorCodes.java
new file mode 100644
index 0000000000000000000000000000000000000000..893b6bd5ef3444b8d23880e0da8bf41d75a6ed88
--- /dev/null
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/ErrorCodes.java
@@ -0,0 +1,7 @@
+package de.vipra.cmd;
+
+public enum ErrorCodes {
+	NONE,
+	CANNOT_LOCK,
+	CANNOT_UNLOCK
+}
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/Main.java b/vipra-cmd/src/main/java/de/vipra/cmd/Main.java
index e6cb162db1b4ece5987e89c931e5f67844825743..8c8d894f8d1a24438a879975a7a4af537ba8e69b 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/Main.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/Main.java
@@ -1,6 +1,5 @@
 package de.vipra.cmd;
 
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.ListIterator;
@@ -16,22 +15,46 @@ import de.vipra.cmd.option.EditModelCommand;
 import de.vipra.cmd.option.ImportCommand;
 import de.vipra.cmd.option.IndexingCommand;
 import de.vipra.cmd.option.ListModelsCommand;
+import de.vipra.cmd.option.MessageCommand;
 import de.vipra.cmd.option.ModelingCommand;
 import de.vipra.cmd.option.PrintModelCommand;
 import de.vipra.cmd.option.TestCommand;
 import de.vipra.util.ConsoleUtils;
-import de.vipra.util.ex.ConfigException;
+import de.vipra.util.LockFile;
+import de.vipra.util.ex.LockFileException;
 
 public class Main {
 
+	private static final LockFile lockFile = new LockFile();
+
 	static {
 		// set morphia log level
 		MorphiaLoggerFactory.registerLogger(SLF4JLoggerImplFactory.class);
+
 		// set corenlp log level, close stderr to mute corenlp messages
 		System.err.close();
+
+	}
+
+	private static void lock() {
+		try {
+			lockFile.lock();
+		} catch (final LockFileException e1) {
+			ConsoleUtils.error("Cannot acquire lock. Is vipra already running? If not, delete the file '" + lockFile.getPath() + "'");
+			System.exit(ErrorCodes.CANNOT_LOCK.ordinal());
+		}
 	}
 
-	public static void main(final String[] args) throws IOException, ConfigException {
+	private static void unlock() {
+		try {
+			lockFile.unlock();
+		} catch (final LockFileException e) {
+			ConsoleUtils.error("Cannot delete lock file. Delete the file '" + lockFile.getPath() + "'");
+			System.exit(ErrorCodes.CANNOT_UNLOCK.ordinal());
+		}
+	}
+
+	private static void execute(final String[] args) {
 		final CommandLineOptions opts = new CommandLineOptions();
 		try {
 			opts.parse(args);
@@ -57,6 +80,9 @@ public class Main {
 		if (opts.isTest())
 			commands.add(new TestCommand());
 
+		if (opts.isMessage())
+			commands.add(new MessageCommand(opts.messageToSend()));
+
 		if (opts.isClear())
 			commands.add(new ClearCommand());
 
@@ -106,4 +132,10 @@ public class Main {
 		}
 	}
 
+	public static void main(final String[] args) {
+		lock();
+		execute(args);
+		unlock();
+	}
+
 }
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java b/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java
index 8bbdec95453a37b7fbbbadd4c1cce7bd8a9f4761..f7fd26ca3c72b076baf4b55b12abf9b57aa7ebcc 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java
@@ -373,7 +373,7 @@ public class Analyzer {
 					reducedShare += topicDistribution[idxTopic];
 					final TopicShare newTopicRef = new TopicShare();
 					final TopicFull topicFull = newTopics.get(idxTopic);
-					Integer articlesCount = topicFull.getArticlesCount();
+					final Integer articlesCount = topicFull.getArticlesCount();
 					topicFull.setArticlesCount(articlesCount == null ? 1 : articlesCount + 1);
 					newTopicRef.setTopic(new Topic(topicFull.getId()));
 					newTopicRef.setShare(topicDistribution[idxTopic]);
@@ -436,22 +436,22 @@ public class Analyzer {
 
 		// remove unreferenced topics
 
-		for (ListIterator<TopicFull> iter = newTopics.listIterator(); iter.hasNext();) {
-			TopicFull topic = iter.next();
-			Integer articlesCount = topic.getArticlesCount();
+		for (final ListIterator<TopicFull> iter = newTopics.listIterator(); iter.hasNext();) {
+			final TopicFull topic = iter.next();
+			final Integer articlesCount = topic.getArticlesCount();
 			if (articlesCount == null || articlesCount == 0)
 				iter.remove();
 		}
 
 		// calculate topic similarities
 
-		int topicMinCount = (int) Math.ceil(topicCount * (1 - modelConfig.getMaxSimilarTopicsDivergence()));
+		final int topicMinCount = (int) Math.ceil(topicCount * (1 - modelConfig.getMaxSimilarTopicsDivergence()));
 
-		for (TopicFull topic1 : newTopics) {
+		for (final TopicFull topic1 : newTopics) {
 			final List<TopicShare> similarTopics = new ArrayList<>();
-			for (TopicFull topic2 : newTopics) {
+			for (final TopicFull topic2 : newTopics) {
 				if (!topic1.getId().equals(topic2.getId())) {
-					Integer count = topicShareMatrix.get(topic1.getId(), topic2.getId());
+					final Integer count = topicShareMatrix.get(topic1.getId(), topic2.getId());
 					if (count != null && count >= topicMinCount) {
 						final TopicShare newTopicShare = new TopicShare();
 						newTopicShare.setTopic(new Topic(topic2.getId()));
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java
index 53f1ac3aab41d39f63b1b0d7ea9b1372efb73f9d..92b2636e6901e8f9a19e3ad615a6aa905e08ad4d 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java
@@ -31,6 +31,9 @@ import de.vipra.util.Config;
 import de.vipra.util.ConsoleUtils;
 import de.vipra.util.Constants;
 import de.vipra.util.Constants.ProcessorMode;
+import de.vipra.util.IPCClient;
+import de.vipra.util.IPCMessage;
+import de.vipra.util.IPCMessageCode;
 import de.vipra.util.StringUtils;
 import de.vipra.util.Timer;
 import de.vipra.util.ex.ConfigException;
@@ -344,9 +347,13 @@ public class ImportCommand implements Command {
 		dbWords = MongoService.getDatabaseService(config, WordFull.class);
 		dbWindows = MongoService.getDatabaseService(config, WindowFull.class);
 		processor = new Processor();
+		final IPCClient ipcClient = new IPCClient();
 		for (final TopicModelConfig modelConfig : config.getTopicModelConfigs(models)) {
+			ipcClient.send(new IPCMessage(IPCMessageCode.START_IMPORTING).message("Started importing for model '" + modelConfig.getName() + "'"));
 			importForModel(modelConfig);
+			ipcClient.send(new IPCMessage(IPCMessageCode.STOP_IMPORTING).message("Finished importing for model '" + modelConfig.getName() + "'"));
 		}
+		ipcClient.close();
 	}
 
 }
\ No newline at end of file
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/IndexingCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/IndexingCommand.java
index 112c38f759354ad0910f51fd4d6299863cbc82ba..f2792a843c1d708a7ac0eaf518184aafaa4d4b92 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/option/IndexingCommand.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/IndexingCommand.java
@@ -15,6 +15,9 @@ import de.vipra.util.Config;
 import de.vipra.util.ConsoleUtils;
 import de.vipra.util.ESClient;
 import de.vipra.util.ESSerializer;
+import de.vipra.util.IPCClient;
+import de.vipra.util.IPCMessage;
+import de.vipra.util.IPCMessageCode;
 import de.vipra.util.MongoUtils;
 import de.vipra.util.StringUtils;
 import de.vipra.util.Timer;
@@ -91,9 +94,13 @@ public class IndexingCommand implements Command {
 		dbArticles = MongoService.getDatabaseService(config, ArticleFull.class);
 		elasticClient = ESClient.getClient(config);
 		elasticSerializer = new ESSerializer<>(ArticleFull.class);
+		final IPCClient ipcClient = new IPCClient();
 		for (final TopicModelConfig modelConfig : config.getTopicModelConfigs(models)) {
+			ipcClient.send(new IPCMessage(IPCMessageCode.START_INDEXING).message("Started indexing model '" + modelConfig.getName() + "'"));
 			indexForModel(modelConfig);
+			ipcClient.send(new IPCMessage(IPCMessageCode.STOP_INDEXING).message("Finished indexing model '" + modelConfig.getName() + "'"));
 		}
+		ipcClient.close();
 	}
 
 }
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/MessageCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/MessageCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..2eba9b2425a84a92427e67a3e31870b829dd02e3
--- /dev/null
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/MessageCommand.java
@@ -0,0 +1,22 @@
+package de.vipra.cmd.option;
+
+import de.vipra.util.IPCClient;
+import de.vipra.util.IPCMessage;
+import de.vipra.util.IPCMessageCode;
+
+public class MessageCommand implements Command {
+
+	private final String message;
+
+	public MessageCommand(final String message) {
+		this.message = message;
+	}
+
+	@Override
+	public void run() throws Exception {
+		final IPCClient client = new IPCClient();
+		client.send(new IPCMessage(IPCMessageCode.MESSAGE).message(message));
+		client.close();
+	}
+
+}
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/ModelingCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/ModelingCommand.java
index 7ab98e13e075f2fa96de884402f4feeb433ad4ad..3bf6fa11dbb5cad03fe5b6275182dd8226857011 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/option/ModelingCommand.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/ModelingCommand.java
@@ -7,6 +7,9 @@ import de.vipra.cmd.lda.Analyzer;
 import de.vipra.cmd.lda.AnalyzerException;
 import de.vipra.util.Config;
 import de.vipra.util.ConsoleUtils;
+import de.vipra.util.IPCClient;
+import de.vipra.util.IPCMessage;
+import de.vipra.util.IPCMessageCode;
 import de.vipra.util.StringUtils;
 import de.vipra.util.Timer;
 import de.vipra.util.ex.ConfigException;
@@ -51,9 +54,13 @@ public class ModelingCommand implements Command {
 	@Override
 	public void run() throws Exception {
 		final Config config = Config.getConfig();
+		final IPCClient ipcClient = new IPCClient();
 		for (final TopicModelConfig modelConfig : config.getTopicModelConfigs(models)) {
+			ipcClient.send(new IPCMessage(IPCMessageCode.START_MODELING).message("Started generating model '" + modelConfig.getName() + "'"));
 			modelForModel(modelConfig);
+			ipcClient.send(new IPCMessage(IPCMessageCode.STOP_MODELING).message("Finished generating model '" + modelConfig.getName() + "'"));
 		}
+		ipcClient.close();
 	}
 
 }
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/TestCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/TestCommand.java
index 38a8b374d0c24fc291cfd8e52eaa4ac24ff0e020..d3d51d54a6c927a62741ca13a1a947c345af3852 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/option/TestCommand.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/TestCommand.java
@@ -1,12 +1,22 @@
 package de.vipra.cmd.option;
 
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
 import org.bson.types.ObjectId;
 import org.elasticsearch.client.transport.NoNodeAvailableException;
 import org.elasticsearch.client.transport.TransportClient;
+import org.fusesource.jansi.Ansi;
+import org.fusesource.jansi.Ansi.Color;
 
 import de.vipra.util.Config;
 import de.vipra.util.ConsoleUtils;
 import de.vipra.util.ESClient;
+import de.vipra.util.IPCClient;
+import de.vipra.util.IPCMessage;
+import de.vipra.util.IPCMessageCode;
 import de.vipra.util.model.Article;
 import de.vipra.util.service.MongoService;
 
@@ -14,23 +24,57 @@ public class TestCommand implements Command {
 
 	@Override
 	public void run() throws Exception {
-		// test if configuration readable
-		ConsoleUtils.info("reading configuration...");
-		final Config config = Config.getConfig();
-
-		// test if database is accessible
-		ConsoleUtils.info("testing mongodb connection...");
-		final MongoService<Article, ObjectId> dbArticles = MongoService.getDatabaseService(config, Article.class);
-		dbArticles.count(null);
-
-		// test if elasticsearch is accessible
-		ConsoleUtils.info("testing elasticsearch connection...");
-		final TransportClient esclient = ESClient.getClient(config);
-		if (esclient.connectedNodes().isEmpty()) {
-			throw new NoNodeAvailableException("no elasticsearch nodes available");
-		}
+		try {
+			// test if configuration readable
+			ConsoleUtils.infoNOLF("reading configuration...");
+			final Config config = Config.getConfig(true);
+			ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString());
+
+			// test if dtm is accessible
+			ConsoleUtils.infoNOLF("testing dtm binary...");
+			if (config.getDtmPath() == null || config.getDtmPath().isEmpty())
+				throw new Exception("dtm binary not configured, set 'dtmPath' in config.json");
+			final File dtm = new File(config.getDtmPath());
+			if (!dtm.exists())
+				throw new FileNotFoundException("dtm binary not available at path: '" + config.getDtmPath() + "'");
+			ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString());
+
+			// test if database is accessible
+			ConsoleUtils.infoNOLF("testing mongodb connection...");
+			final MongoService<Article, ObjectId> dbArticles = MongoService.getDatabaseService(config, Article.class);
+			dbArticles.count(null);
+			ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString());
 
-		ConsoleUtils.info("all tests passed");
+			// test if ipc is accessible
+			ConsoleUtils.infoNOLF("testing ipc connection...");
+			final IPCClient client = new IPCClient();
+			client.send(new IPCMessage(IPCMessageCode.TEST));
+			client.close();
+			ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString());
+
+			// test if elasticsearch is accessible
+			ConsoleUtils.infoNOLF("testing elasticsearch connection...");
+			final TransportClient esclient = ESClient.getClient(config);
+			if (esclient.connectedNodes().isEmpty())
+				throw new NoNodeAvailableException("no elasticsearch nodes available");
+			ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString());
+
+			// test if spotlight is accessible
+			if (config.isSpotlightEnabled()) {
+				ConsoleUtils.infoNOLF("testing spotlight connection...");
+				final URL url = new URL(config.getSpotlightUrl() + "/rest/application.wadl");
+				final HttpURLConnection huc = (HttpURLConnection) url.openConnection();
+				huc.setRequestMethod("HEAD");
+				if (huc.getResponseCode() != 200)
+					throw new Exception("spotlight server not available");
+				ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString());
+			}
+
+			ConsoleUtils.info("all tests passed");
+		} catch (Exception e) {
+			ConsoleUtils.print(Ansi.ansi().fg(Color.RED).a("FAILED").reset().toString());
+			throw e;
+		}
 	}
 
 }
diff --git a/vipra-cmd/src/main/resources/config.json b/vipra-cmd/src/main/resources/config.json
index ba2f39ecd81d944babd2e72b54baa7d30f11ab65..0bf0320a68e42aecc4b004db2c56e55f1dbe217f 100644
--- a/vipra-cmd/src/main/resources/config.json
+++ b/vipra-cmd/src/main/resources/config.json
@@ -5,8 +5,10 @@
 	"databaseName": "test",
 	"elasticSearchHost": "127.0.0.1",
 	"elasticSearchPort": 9300,
+	"ipcHost": "127.0.0.1",
+	"ipcPort": 6789,
 	"spotlightUrl": "",
-	"dtmPath": "",
+	"dtmPath": "/opt/vipra/dtm_release/dtm/main",
 	"modelConfigTemplate": {
 		"name": "",
 		"kTopics": 20,
diff --git a/vipra-ui/app/js/app.js b/vipra-ui/app/js/app.js
index a5374727954992cdf9090a1efd66fcda9b35917a..25a3d9b9a405691cf9e825e3018dc7f5d347a343 100644
--- a/vipra-ui/app/js/app.js
+++ b/vipra-ui/app/js/app.js
@@ -2,7 +2,7 @@
  * Vipra Application
  * Main application file
  ******************************************************************************/
-/* globals angular, $ */
+/* globals angular, $, Vipra */
 (function() {
 
   "use strict";
@@ -11,6 +11,7 @@
     'ngResource',
     'ngSanitize',
     'ngAnimate',
+    'ngWebSocket',
     'ui.router',
     'cfp.hotkeys',
     'nya.bootstrap.select',
@@ -200,7 +201,7 @@
     }
   ]);
 
-  app.run(['$rootScope', '$state', function($rootScope, $state) {
+  app.run(['$rootScope', '$state', '$websocket', function($rootScope, $state, $websocket) {
 
     $rootScope.loading = {};
 
@@ -223,6 +224,12 @@
       $rootScope.alerts = [];
     });
 
+    var socket = $websocket(Vipra.config.websocketUrl);
+
+    socket.onMessage(function(message) {
+      console.log(message.data);
+    });
+
   }]);
 
   $(document).on('change', '.btn-file :file', function() {
diff --git a/vipra-ui/app/js/config.js b/vipra-ui/app/js/config.js
index 397d96104139282185428e5b6d7e7bbb26025955..6a10054bf69fd58cbcb0b0a6eb2d8df80251ab43 100644
--- a/vipra-ui/app/js/config.js
+++ b/vipra-ui/app/js/config.js
@@ -19,7 +19,12 @@
      *                      ^^^^^ 
      *                      this is the restUrl
      */
-    restUrl: '/rest'
+    restUrl: '/rest',
+
+    /*
+     * Point this URL to the backend websocket. The default is /ws.
+     */
+    websocketUrl: '/ws'
   };
 
 })();
\ No newline at end of file
diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less
index c846d7cb5197b870ea0dd9a54202f0b4c103f25c..4534a0062003ba89e6c407f7b4be94ae7092c26f 100644
--- a/vipra-ui/app/less/app.less
+++ b/vipra-ui/app/less/app.less
@@ -14,6 +14,14 @@ body {
   padding-bottom: 20px;
 }
 
+html, body {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+
+svg {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important;
+}
+
 .info,
 .pointer,
 input[type=checkbox],
@@ -235,6 +243,12 @@ a:hover {
   display: inline-block;
   width: 100%;
   vertical-align: middle;
+  position: relative;
+
+  .badge {
+    position: absolute;
+    right: 0;
+  }
 }
 
 .colorbox {
@@ -713,8 +727,11 @@ entity-menu {
 
 .index {
   .navbar-default {
-    border-color: transparent;
+    border: 0;
     background: rgba(255,255,255,0.6);
+    .navbar-nav>li>a {
+      color: #555;
+    }
   }
 }
 
diff --git a/vipra-ui/bower.json b/vipra-ui/bower.json
index 160ceb62dc785c0b0244357cfcb83f66e1e9c294..1d60c9d9fd99051f4f9439f28f6a4c7508a8d2c0 100644
--- a/vipra-ui/bower.json
+++ b/vipra-ui/bower.json
@@ -22,6 +22,8 @@
     "angular": "^1.x",
     "angular-resource": "^1.x",
     "angular-sanitize": "^1.x",
+    "angular-animate": "^1.x",
+    "angular-websocket": "^1.x",
     "angular-ui-router": "^0.x",
     "highcharts": "^4.x",
     "vis": "^4.x",
@@ -32,7 +34,6 @@
     "randomcolor": "randomColor#^0.x",
     "bootbox.js": "bootbox#^4.x",
     "angular-hotkeys": "chieffancypants/angular-hotkeys#^1.x",
-    "eonasdan-bootstrap-datetimepicker": "^4.17.37",
-    "angular-animate": "^1.5.5"
+    "eonasdan-bootstrap-datetimepicker": "^4.x"
   }
 }
diff --git a/vipra-ui/build.sh b/vipra-ui/build.sh
index ee2f1c881fd2a425cb23141b67e71594b77bcc08..511106b8b0612914b99f1327d6c685dd4aadad3e 100755
--- a/vipra-ui/build.sh
+++ b/vipra-ui/build.sh
@@ -5,5 +5,6 @@ rm -rf public
 npm install
 bower install
 gulp build
+gulp compress
 
 exit 0
diff --git a/vipra-ui/gulpfile.js b/vipra-ui/gulpfile.js
index fb129804a544517dfc9e7eca0156ac7e21e9761b..6d8a564aa7e222ec94e1718b01b9f25e7b76123a 100644
--- a/vipra-ui/gulpfile.js
+++ b/vipra-ui/gulpfile.js
@@ -1,6 +1,7 @@
 /* jshint ignore: start */
 
 var gulp = require('gulp'),
+  gzip = require('gulp-gzip'),
   less = require('gulp-less'),
   concat = require('gulp-concat'),
   uglify = require('gulp-uglify'),
@@ -16,6 +17,7 @@ var assets = {
     'bower_components/angular-resource/angular-resource.min.js',
     'bower_components/angular-sanitize/angular-sanitize.min.js',
     'bower_components/angular-animate/angular-animate.min.js',
+    'bower_components/angular-websocket/angular-websocket.min.js',
     'bower_components/angular-hotkeys/build/hotkeys.min.js',
     'bower_components/angular-ui-router/release/angular-ui-router.min.js',
     'bower_components/bootstrap/dist/js/bootstrap.min.js',
@@ -44,6 +46,13 @@ var assets = {
   img: []
 };
 
+var gzip_options = {
+  threshold: 1400,
+  gzipOptions: {
+    level: 6
+  }
+};
+
 gulp.task('less', function() {
   gulp.src('app/less/**/*.less')
     .pipe(concat('app.css'))
@@ -65,8 +74,10 @@ gulp.task('js-rel', function() {
   gulp.src(['app/js/**/*.js', '!app/js/config.js'])
     .pipe(concat('app.js'))
     .pipe(ngannotate())
+    .pipe(uglify())
     .pipe(gulp.dest('public/js'));
   gulp.src('app/js/config.js')
+    .pipe(uglify())
     .pipe(gulp.dest('public/js'));
 });
 
@@ -108,7 +119,42 @@ gulp.task('assets', function() {
     .pipe(gulp.dest('public/img'));
 });
 
-gulp.task('build', ['less', 'js-rel', 'html', 'img', 'public', 'assets']);
+gulp.task('assets-rel', function() {
+  gulp.src(assets.js)
+    .pipe(concat('vendor.js'))
+    .pipe(uglify())
+    .pipe(gulp.dest('public/js'));
+  gulp.src(assets.css)
+    .pipe(cleancss())
+    .pipe(concat('vendor.css'))
+    .pipe(gulp.dest('public/css'));
+  gulp.src(assets.fonts)
+    .pipe(gulp.dest('public/fonts'));
+  gulp.src(assets.img)
+    .pipe(gulp.dest('public/img'));
+});
+
+gulp.task('compress', function() {
+  gulp.src('public/css/**/*')
+    .pipe(gzip(gzip_options))
+    .pipe(gulp.dest('public/css'));
+  gulp.src('public/fonts/**/*')
+    .pipe(gzip(gzip_options))
+    .pipe(gulp.dest('public/fonts'));
+  gulp.src('public/html/**/*')
+    .pipe(gzip(gzip_options))
+    .pipe(gulp.dest('public/html'));
+  gulp.src('public/js/**/*')
+    .pipe(gzip(gzip_options))
+    .pipe(gulp.dest('public/js'));
+  gulp.src('public/*')
+    .pipe(gzip(gzip_options))
+    .pipe(gulp.dest('public'));
+});
+
+gulp.task('build-dev', ['less', 'js', 'html', 'img', 'public', 'assets']);
+
+gulp.task('build', ['less', 'js-rel', 'html', 'img', 'public', 'assets-rel']);
 
 gulp.task('watch', function() {
   gulp.watch('app/less/**/*.less', ['less']);
diff --git a/vipra-ui/package.json b/vipra-ui/package.json
index 87e1b48fb71a60a14f22100337443935cee55579..6a28e95a2321e383701656553b6cbd9dd633794f 100644
--- a/vipra-ui/package.json
+++ b/vipra-ui/package.json
@@ -11,6 +11,7 @@
     "gulp-clean-css": "^2.0.3",
     "gulp-concat": "^2.6.0",
     "gulp-cssnano": "^2.1.0",
+    "gulp-gzip": "^1.2.0",
     "gulp-include": "^2.1.0",
     "gulp-less": "^3.0.5",
     "gulp-ng-annotate": "^2.0.0",
diff --git a/vipra-util/src/main/java/de/vipra/util/Config.java b/vipra-util/src/main/java/de/vipra/util/Config.java
index 391410401838bbb89e7eb949d3e576edd91e1da5..55fec90e8ae55c106a1c47135f74398797d36cb7 100644
--- a/vipra-util/src/main/java/de/vipra/util/Config.java
+++ b/vipra-util/src/main/java/de/vipra/util/Config.java
@@ -42,6 +42,8 @@ public class Config {
 	private String databaseName = Constants.DATABASE_NAME;
 	private String elasticSearchHost = Constants.ES_HOST;
 	private int elasticSearchPort = Constants.ES_PORT;
+	private String ipcHost = Constants.IPC_HOST;
+	private int ipcPort = Constants.IPC_PORT;
 	private TopicModelConfig modelConfigTemplate = new TopicModelConfig();
 	private String spotlightUrl;
 	private String dtmPath;
@@ -89,6 +91,26 @@ public class Config {
 		this.elasticSearchPort = elasticSearchPort;
 	}
 
+	public String getIpcHost() {
+		return ipcHost;
+	}
+
+	public void setIpcHost(final String ipcHost) {
+		this.ipcHost = ipcHost;
+	}
+
+	public int getIpcPort() {
+		return ipcPort;
+	}
+
+	public void setIpcPort(final int ipcPort) {
+		this.ipcPort = ipcPort;
+	}
+
+	public boolean isSpotlightEnabled() {
+		return spotlightUrl != null && !spotlightUrl.isEmpty();
+	}
+
 	public String getSpotlightUrl() {
 		return spotlightUrl;
 	}
@@ -212,6 +234,10 @@ public class Config {
 	}
 
 	public static Config getConfig() throws ConfigException {
+		return getConfig(false);
+	}
+
+	public static Config getConfig(boolean skipModels) throws ConfigException {
 		if (instance == null) {
 			try {
 				InputStream in = null;
@@ -255,12 +281,14 @@ public class Config {
 					throw new ConfigException("could not read configuration");
 
 				// read topic model configs
-				final MongoService<TopicModelFull, String> dbTopicModels = MongoService.getDatabaseService(instance, TopicModelFull.class);
-				final List<TopicModelFull> topicModels = dbTopicModels.getAll("_all");
-				final Map<String, TopicModelConfig> topicModelConfigs = new HashMap<>(topicModels.size());
-				for (final TopicModelFull topicModel : topicModels)
-					topicModelConfigs.put(topicModel.getId(), topicModel.getModelConfig());
-				instance.setTopicModelConfigs(topicModelConfigs);
+				if (!skipModels) {
+					final MongoService<TopicModelFull, String> dbTopicModels = MongoService.getDatabaseService(instance, TopicModelFull.class);
+					final List<TopicModelFull> topicModels = dbTopicModels.getAll("_all");
+					final Map<String, TopicModelConfig> topicModelConfigs = new HashMap<>(topicModels.size());
+					for (final TopicModelFull topicModel : topicModels)
+						topicModelConfigs.put(topicModel.getId(), topicModel.getModelConfig());
+					instance.setTopicModelConfigs(topicModelConfigs);
+				}
 			} catch (final IOException e) {
 				throw new ConfigException(e);
 			}
diff --git a/vipra-util/src/main/java/de/vipra/util/ConsoleUtils.java b/vipra-util/src/main/java/de/vipra/util/ConsoleUtils.java
index 395375440c6202abedfcc0f7d82ccfd7c03a384f..c751672eb648876368351be553c9586b722c349f 100644
--- a/vipra-util/src/main/java/de/vipra/util/ConsoleUtils.java
+++ b/vipra-util/src/main/java/de/vipra/util/ConsoleUtils.java
@@ -93,6 +93,14 @@ public class ConsoleUtils {
 		return error(t.getMessage());
 	}
 
+	public static int print(final String msg) {
+		return print(System.out, true, msg);
+	}
+	
+	public static int printNOLF(final String msg) {
+		return print(System.out, false, msg);
+	}
+
 	public static double readDouble(String message, final Double def, final Double min, final Double max, final boolean showBounds) {
 		final BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
 		if (showBounds)
diff --git a/vipra-util/src/main/java/de/vipra/util/Constants.java b/vipra-util/src/main/java/de/vipra/util/Constants.java
index 936f6b4e432ed6977afc9e5f2a979215d59f2ddb..edc7792a412a6f7679412e04ce9e3919b6bc78bf 100644
--- a/vipra-util/src/main/java/de/vipra/util/Constants.java
+++ b/vipra-util/src/main/java/de/vipra/util/Constants.java
@@ -45,6 +45,12 @@ public class Constants {
 	public static final String ES_HOST = "127.0.0.1";
 	public static final int ES_PORT = 9300;
 
+	/*
+	 * IPC
+	 */
+	public static final String IPC_HOST = "127.0.0.1";
+	public static final int IPC_PORT = 6789;
+
 	/**
 	 * Topic boost parameter. Boosts topic importance in queries. Default 4.
 	 */
diff --git a/vipra-util/src/main/java/de/vipra/util/CountMatrix.java b/vipra-util/src/main/java/de/vipra/util/CountMatrix.java
index 7177ddc24eb8d51b497054ba22ffaf91e55cecae..feac46bf448144a649be801818174e6b5f213384 100644
--- a/vipra-util/src/main/java/de/vipra/util/CountMatrix.java
+++ b/vipra-util/src/main/java/de/vipra/util/CountMatrix.java
@@ -2,7 +2,7 @@ package de.vipra.util;
 
 public class CountMatrix<T, U> extends Matrix<T, U, Integer> {
 
-	public void count(T t, U u) {
+	public void count(final T t, final U u) {
 		Integer i = get(t, u);
 		if (i == null)
 			i = 1;
diff --git a/vipra-util/src/main/java/de/vipra/util/IPCChain.java b/vipra-util/src/main/java/de/vipra/util/IPCChain.java
new file mode 100644
index 0000000000000000000000000000000000000000..825e3f88f38b6e1f33b9261883bb77f544404764
--- /dev/null
+++ b/vipra-util/src/main/java/de/vipra/util/IPCChain.java
@@ -0,0 +1,7 @@
+package de.vipra.util;
+
+public interface IPCChain {
+
+	void call(IPCMessage message);
+
+}
diff --git a/vipra-util/src/main/java/de/vipra/util/IPCClient.java b/vipra-util/src/main/java/de/vipra/util/IPCClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..46fd449dca538fd1cc81a9b91e8eef3a08654581
--- /dev/null
+++ b/vipra-util/src/main/java/de/vipra/util/IPCClient.java
@@ -0,0 +1,36 @@
+package de.vipra.util;
+
+import java.io.Closeable;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import de.vipra.util.ex.ConfigException;
+
+public class IPCClient implements Closeable {
+
+	private final Socket socket;
+	private final DataOutputStream out;
+
+	public IPCClient() throws ConfigException, UnknownHostException, IOException {
+		final Config config = Config.getConfig();
+		socket = new Socket(config.getIpcHost(), config.getIpcPort());
+		out = new DataOutputStream(socket.getOutputStream());
+	}
+
+	public IPCClient send(final IPCMessage message) throws IOException {
+		if (!socket.isClosed()) {
+			final String messageStr = Config.mapper.writeValueAsString(message);
+			out.writeUTF(messageStr);
+			out.flush();
+		}
+		return this;
+	}
+
+	@Override
+	public void close() throws IOException {
+		socket.close();
+	}
+
+}
diff --git a/vipra-util/src/main/java/de/vipra/util/IPCMessage.java b/vipra-util/src/main/java/de/vipra/util/IPCMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..f26bf871484b3abb0b8cbad7ce2ec0ef426d4c6f
--- /dev/null
+++ b/vipra-util/src/main/java/de/vipra/util/IPCMessage.java
@@ -0,0 +1,36 @@
+package de.vipra.util;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class IPCMessage {
+
+	private IPCMessageCode code;
+
+	private String message;
+
+	public IPCMessage() {}
+
+	public IPCMessage(final IPCMessageCode code) {
+		this.code = code;
+	}
+
+	public IPCMessageCode getCode() {
+		return code;
+	}
+
+	public IPCMessage code(final IPCMessageCode code) {
+		this.code = code;
+		return this;
+	}
+
+	public String getMessage() {
+		return message;
+	}
+
+	public IPCMessage message(final String message) {
+		this.message = message;
+		return this;
+	}
+
+}
diff --git a/vipra-util/src/main/java/de/vipra/util/IPCMessageCode.java b/vipra-util/src/main/java/de/vipra/util/IPCMessageCode.java
new file mode 100644
index 0000000000000000000000000000000000000000..59fb104e6de77561ba5839bc6265a3cdddae2bc0
--- /dev/null
+++ b/vipra-util/src/main/java/de/vipra/util/IPCMessageCode.java
@@ -0,0 +1,12 @@
+package de.vipra.util;
+
+public enum IPCMessageCode {
+	TEST,
+	MESSAGE,
+	START_MODELING,
+	STOP_MODELING,
+	START_INDEXING,
+	STOP_INDEXING,
+	START_IMPORTING,
+	STOP_IMPORTING
+}
\ No newline at end of file
diff --git a/vipra-util/src/main/java/de/vipra/util/IPCServer.java b/vipra-util/src/main/java/de/vipra/util/IPCServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..ba440f5dc163b04a5fb164c6e13e872929824386
--- /dev/null
+++ b/vipra-util/src/main/java/de/vipra/util/IPCServer.java
@@ -0,0 +1,77 @@
+package de.vipra.util;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import de.vipra.util.ex.ConfigException;
+
+public class IPCServer extends Thread {
+
+	public static final Logger log = LoggerFactory.getLogger(IPCServer.class);
+	private static IPCServer instance;
+
+	private final Map<String, IPCChain> chains = new LinkedHashMap<>();
+
+	@SuppressWarnings("resource")
+	@Override
+	public void run() {
+		Config config = null;
+		ServerSocket welcomeSocket = null;
+		try {
+			config = Config.getConfig();
+			welcomeSocket = new ServerSocket(config.getIpcPort());
+		} catch (ConfigException | IOException e) {
+			log.error(e.getMessage());
+		}
+
+		while (true) {
+			try {
+				final Socket connectionSocket = welcomeSocket.accept();
+				final DataInputStream in = new DataInputStream(connectionSocket.getInputStream());
+				final String input = in.readUTF();
+				final IPCMessage message = Config.mapper.readValue(input, IPCMessage.class);
+				if (message.getCode() != IPCMessageCode.TEST)
+					handleMessage(message);
+			} catch (final IOException e) {
+				log.error(e.getMessage());
+			}
+		}
+	}
+
+	@Override
+	public void start() {
+		if (!isAlive())
+			super.start();
+	}
+
+	public void register(final String id, final IPCChain chain) {
+		chains.put(id, chain);
+	}
+
+	private void handleMessage(final IPCMessage message) {
+		log.info("message received: " + message.getCode());
+		for (final Entry<String, IPCChain> chain : chains.entrySet()) {
+			try {
+				chain.getValue().call(message);
+			} catch (final Exception e) {
+				log.error(e.getMessage());
+			}
+		}
+	}
+
+	public static IPCServer getInstance() {
+		if (instance == null) {
+			instance = new IPCServer();
+		}
+		return instance;
+	}
+
+}
diff --git a/vipra-util/src/main/java/de/vipra/util/LockFile.java b/vipra-util/src/main/java/de/vipra/util/LockFile.java
new file mode 100644
index 0000000000000000000000000000000000000000..216989340578db7cf236ada2282da927d274efcc
--- /dev/null
+++ b/vipra-util/src/main/java/de/vipra/util/LockFile.java
@@ -0,0 +1,34 @@
+package de.vipra.util;
+
+import java.io.File;
+import java.io.IOException;
+
+import de.vipra.util.ex.LockFileException;
+
+public class LockFile {
+
+	private File file = new File(PathUtils.tempDir(), "vipra.lock");
+
+	public void lock() throws LockFileException {
+		if (file.exists())
+			throw new LockFileException();
+		try {
+			file.createNewFile();
+		} catch (final IOException e) {
+			throw new LockFileException(e);
+		}
+	}
+
+	public void unlock() throws LockFileException {
+		try {
+			file.delete();
+		} catch (final SecurityException e) {
+			throw new LockFileException(e);
+		}
+	}
+
+	public String getPath() {
+		return file.getAbsolutePath();
+	}
+
+}
diff --git a/vipra-util/src/main/java/de/vipra/util/Matrix.java b/vipra-util/src/main/java/de/vipra/util/Matrix.java
index adfb46443392fa933efa510cb39c473686701dde..3d3375149d9a22da19f9c41f783000a0c32ca74c 100644
--- a/vipra-util/src/main/java/de/vipra/util/Matrix.java
+++ b/vipra-util/src/main/java/de/vipra/util/Matrix.java
@@ -16,14 +16,14 @@ public class Matrix<T, U, V> {
 		colMap = new HashMap<>();
 	}
 
-	public Matrix(int rowSize, int colSize) {
+	public Matrix(final int rowSize, final int colSize) {
 		rowMap = new HashMap<>(rowSize);
 		colMap = new HashMap<>(colSize);
 		startRowSize = rowSize;
 		startColSize = colSize;
 	}
 
-	public V put(T t, U u, V v) {
+	public V put(final T t, final U u, final V v) {
 		Map<U, V> row = rowMap.get(t);
 		Map<T, V> col = colMap.get(u);
 		V oldV = null;
@@ -42,8 +42,8 @@ public class Matrix<T, U, V> {
 		return oldV;
 	}
 
-	public V get(T t, U u) {
-		Map<U, V> subMap = rowMap.get(t);
+	public V get(final T t, final U u) {
+		final Map<U, V> subMap = rowMap.get(t);
 		if (subMap == null)
 			return null;
 		return subMap.get(u);
@@ -51,16 +51,16 @@ public class Matrix<T, U, V> {
 
 	public int size() {
 		int size = 0;
-		for (Map<U, V> subMap : rowMap.values())
+		for (final Map<U, V> subMap : rowMap.values())
 			size += subMap.size();
 		return size;
 	}
 
-	public Map<U, V> row(T t) {
+	public Map<U, V> row(final T t) {
 		return rowMap.get(t);
 	}
 
-	public Map<T, V> col(U u) {
+	public Map<T, V> col(final U u) {
 		return colMap.get(u);
 	}
 
diff --git a/vipra-util/src/main/java/de/vipra/util/PathUtils.java b/vipra-util/src/main/java/de/vipra/util/PathUtils.java
index 4275979ab03b3c9952f77d94a8491db0e5db522c..63ade27aa543b6b786bba6e6ddff0fba8c5c3700 100644
--- a/vipra-util/src/main/java/de/vipra/util/PathUtils.java
+++ b/vipra-util/src/main/java/de/vipra/util/PathUtils.java
@@ -30,4 +30,8 @@ public class PathUtils {
 		return base;
 	}
 
+	public static File tempDir() {
+		return new File(System.getProperty("java.io.tmpdir"));
+	}
+
 }
diff --git a/vipra-util/src/main/java/de/vipra/util/ex/LockFileException.java b/vipra-util/src/main/java/de/vipra/util/ex/LockFileException.java
new file mode 100644
index 0000000000000000000000000000000000000000..780d7472a3a504a6f273c5caec30734a89e93040
--- /dev/null
+++ b/vipra-util/src/main/java/de/vipra/util/ex/LockFileException.java
@@ -0,0 +1,16 @@
+package de.vipra.util.ex;
+
+@SuppressWarnings("serial")
+public class LockFileException extends Exception {
+
+	public LockFileException() {}
+
+	public LockFileException(final String msg) {
+		super(msg);
+	}
+
+	public LockFileException(final Throwable t) {
+		super(t);
+	}
+
+}
diff --git a/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java b/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java
index 1f905fc7d0429b817403f8887d511d9825102653..a4bf05c0ce1aa10a927616d10f2305eb03809045 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java
@@ -163,7 +163,7 @@ public class ArticleFull implements Model<ObjectId>, Serializable {
 		return window;
 	}
 
-	public void setWindow(Window window) {
+	public void setWindow(final Window window) {
 		this.window = window;
 	}
 
@@ -210,7 +210,7 @@ public class ArticleFull implements Model<ObjectId>, Serializable {
 		final List<String> topics = new ArrayList<>(refs.size());
 		for (final TopicShare ref : refs) {
 			final Topic topic = ref.getTopic();
-			if(topic != null)
+			if (topic != null)
 				topics.add(topic.getName());
 		}
 		return topics.toArray(new String[topics.size()]);
diff --git a/vipra-util/src/main/java/de/vipra/util/model/ArticleStats.java b/vipra-util/src/main/java/de/vipra/util/model/ArticleStats.java
index c8d25ee66bf0a85c78941779917aee8068219501..9044fb551c0aa9f703230b9a2dcdc8e688d8ff7b 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/ArticleStats.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/ArticleStats.java
@@ -13,11 +13,11 @@ public class ArticleStats implements Serializable {
 	private static final long serialVersionUID = -4712841724990200627L;
 
 	private Long wordCount;
-	
+
 	private Long uniqueWordCount;
-	
+
 	private Long processedWordCount;
-	
+
 	private Double reductionRatio;
 
 	public Long getWordCount() {
diff --git a/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java b/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java
index 6424111e156e32ed7a701fab8a4af7da0f856ec7..5947d68087714e087ccc46dca28fc46927103f1c 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java
@@ -118,7 +118,7 @@ public class TopicFull implements Model<ObjectId>, Serializable {
 		return similarTopics;
 	}
 
-	public void setSimilarTopics(List<TopicShare> similarTopics) {
+	public void setSimilarTopics(final List<TopicShare> similarTopics) {
 		this.similarTopics = similarTopics;
 	}
 
diff --git a/vipra-util/src/main/java/de/vipra/util/model/WindowFull.java b/vipra-util/src/main/java/de/vipra/util/model/WindowFull.java
index 738b75171d45b6fb7cfed26699e833815f962ed2..be133bdcf06b715aaf23a06e12eabf0aac2e6d5b 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/WindowFull.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/WindowFull.java
@@ -29,11 +29,11 @@ public class WindowFull implements Model<String>, Serializable, Comparable<Windo
 
 	public WindowFull() {}
 
-	public WindowFull(Window window) {
-		this.id = Long.toString(window.getStartDate().getTime());
-		this.startDate = window.getStartDate();
-		this.endDate = window.getEndDate();
-		this.windowResolution = window.getWindowResolution();
+	public WindowFull(final Window window) {
+		id = Long.toString(window.getStartDate().getTime());
+		startDate = window.getStartDate();
+		endDate = window.getEndDate();
+		windowResolution = window.getWindowResolution();
 	}
 
 	@Override
@@ -42,7 +42,7 @@ public class WindowFull implements Model<String>, Serializable, Comparable<Windo
 	}
 
 	@Override
-	public void setId(String id) {
+	public void setId(final String id) {
 		this.id = id;
 	}
 
@@ -50,7 +50,7 @@ public class WindowFull implements Model<String>, Serializable, Comparable<Windo
 		return startDate;
 	}
 
-	public void setStartDate(Date startDate) {
+	public void setStartDate(final Date startDate) {
 		this.startDate = startDate;
 	}
 
@@ -58,7 +58,7 @@ public class WindowFull implements Model<String>, Serializable, Comparable<Windo
 		return endDate;
 	}
 
-	public void setEndDate(Date endDate) {
+	public void setEndDate(final Date endDate) {
 		this.endDate = endDate;
 	}
 
@@ -66,7 +66,7 @@ public class WindowFull implements Model<String>, Serializable, Comparable<Windo
 		return windowResolution;
 	}
 
-	public void setWindowResolution(WindowResolution windowResolution) {
+	public void setWindowResolution(final WindowResolution windowResolution) {
 		this.windowResolution = windowResolution;
 	}
 
@@ -74,12 +74,12 @@ public class WindowFull implements Model<String>, Serializable, Comparable<Windo
 		return topicModel;
 	}
 
-	public void setTopicModel(TopicModel topicModel) {
+	public void setTopicModel(final TopicModel topicModel) {
 		this.topicModel = topicModel;
 	}
 
 	@Override
-	public int compareTo(WindowFull o) {
+	public int compareTo(final WindowFull o) {
 		return startDate.compareTo(o.getStartDate());
 	}
 }
diff --git a/vipra-util/src/main/java/de/vipra/util/model/WordFull.java b/vipra-util/src/main/java/de/vipra/util/model/WordFull.java
index 071156f080592f1cd979b5988bd66df22c9a4611..4f58b20f50cd143d21d9b37653b54798eed7efd4 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/WordFull.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/WordFull.java
@@ -53,7 +53,7 @@ public class WordFull implements Model<String>, Comparable<WordFull>, Serializab
 		return created;
 	}
 
-	public void setCreated(Date created) {
+	public void setCreated(final Date created) {
 		this.created = created;
 	}
 
@@ -61,7 +61,7 @@ public class WordFull implements Model<String>, Comparable<WordFull>, Serializab
 		return modified;
 	}
 
-	public void setModified(Date modified) {
+	public void setModified(final Date modified) {
 		this.modified = modified;
 	}