diff --git a/ma-impl.sublime-workspace b/ma-impl.sublime-workspace
index bd0a79efd0dac011c7b923562854f37b2b355aed..776533a41752c0b806d9d859a69c0ae06499a1b7 100644
--- a/ma-impl.sublime-workspace
+++ b/ma-impl.sublime-workspace
@@ -287,14 +287,6 @@
 	},
 	"buffers":
 	[
-		{
-			"file": "vipra-ui/css/app.less",
-			"settings":
-			{
-				"buffer_size": 1761,
-				"line_ending": "Unix"
-			}
-		}
 	],
 	"build_system": "",
 	"build_system_choices":
@@ -478,31 +470,42 @@
 		"/home/eike/repos/master/ma-impl/vipra-ui/css",
 		"/home/eike/repos/master/ma-impl/vipra-ui/html",
 		"/home/eike/repos/master/ma-impl/vipra-ui/html/articles",
+		"/home/eike/repos/master/ma-impl/vipra-ui/html/directives",
 		"/home/eike/repos/master/ma-impl/vipra-ui/html/topics",
 		"/home/eike/repos/master/ma-impl/vipra-ui/html/words",
-		"/home/eike/repos/master/ma-impl/vipra-ui/js"
+		"/home/eike/repos/master/ma-impl/vipra-ui/js",
+		"/home/eike/repos/master/ma-impl/vm/data"
 	],
 	"file_history":
 	[
-		"/home/eike/repos/master/ma-impl/vipra-ui/html/index.html",
-		"/home/eike/repos/master/ma-impl/vipra-ui/html/words/index.html",
-		"/home/eike/repos/master/ma-impl/vipra-ui/html/topics/index.html",
-		"/home/eike/repos/master/ma-impl/vipra-ui/html/articles/index.html",
+		"/home/eike/repos/master/ma-impl/vipra-ui/js/controllers.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/html/words/show.html",
+		"/home/eike/repos/master/ma-impl/vipra-ui/html/topics/show.html",
 		"/home/eike/repos/master/ma-impl/vipra-ui/js/directives.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/js/app.js",
-		"/home/eike/repos/master/ma-impl/vipra-ui/js/controllers.js",
-		"/home/eike/repos/master/ma-impl/vipra-ui/html/articles/show.html",
-		"/home/eike/repos/master/ma-impl/vipra-ui/css/app.less",
-		"/home/eike/repos/master/ma-impl/vipra-ui/js/filters.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/html/index.html",
 		"/home/eike/repos/master/ma-impl/vipra-ui/index.html",
 		"/home/eike/repos/master/ma-impl/vm/data/data.json",
+		"/home/eike/repos/master/ma-impl/vm/test/2011-q3/4.txt",
+		"/home/eike/repos/master/ma-impl/vm/test/2011-q4/5.txt",
+		"/home/eike/repos/master/ma-impl/vm/test/2011-q1/6.txt",
+		"/home/eike/repos/master/ma-impl/vm/data/test-10.json",
+		"/home/eike/repos/master/ma-impl/vm/test/2011-q1/3.txt",
+		"/home/eike/repos/master/ma-impl/vm/test/2011-q2/2.txt",
+		"/home/eike/repos/master/ma-impl/vm/test/2011-q3/1.txt",
+		"/home/eike/repos/master/ma-impl/vipra-ui/html/articles/index.html",
+		"/home/eike/repos/master/ma-impl/vipra-ui/js/helpers.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/js/filters.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/html/articles/show.html",
+		"/home/eike/repos/master/ma-impl/vipra-ui/css/app.less",
+		"/home/eike/repos/master/ma-impl/vipra-ui/js/services.js",
+		"/home/eike/repos/master/ma-impl/vipra-ui/html/directives/pagination.html",
+		"/home/eike/repos/master/ma-impl/vipra-ui/html/words/index.html",
+		"/home/eike/repos/master/ma-impl/vipra-ui/html/topics/index.html",
 		"/home/eike/repos/master/ma-impl/vm/data/test-1.json",
 		"/home/eike/repos/master/ma-impl/vm/data/test-2.json",
-		"/home/eike/repos/master/ma-impl/vm/data/test-10.json",
 		"/home/eike/repos/master/ma-impl/vipra-ui/js/factories.js",
 		"/run/user/1000/gvfs/smb-share:server=eike-ain,share=share/interceptors.js",
-		"/home/eike/repos/master/ma-impl/vipra-ui/html/topics/show.html",
-		"/home/eike/repos/master/ma-impl/vipra-ui/html/words/show.html",
 		"/home/eike/repos/master/ma-impl/vipra-ui/gulpfile.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/css/footer.less",
 		"/home/eike/repos/master/ma-impl/vipra-ui/less/vendor.less",
@@ -511,7 +514,6 @@
 		"/home/eike/repos/master/ma-impl/vipra-ui/public/index.html",
 		"/home/eike/repos/master/ma-impl/vipra-ui/js/vendor.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/less/main.less",
-		"/home/eike/repos/master/ma-impl/vipra-ui/js/services.js",
 		"/home/eike/repos/master/ma-impl/vipra-ui/css/main.less",
 		"/home/eike/.config/sublime-text-3/Packages/User/Preferences.sublime-settings",
 		"/home/eike/repos/master/ma-impl/vipra-ui/app/adapters/application.js",
@@ -603,15 +605,7 @@
 		"/home/eike/repos/master/ma-impl/vipra-cmd/build2.xml",
 		"/home/eike/repos/master/ma-impl/vipra-ui/README.md",
 		"/home/eike/repos/testasd/bower.json",
-		"/home/eike/repos/master/ma-impl/vipra-ui2/package.json",
-		"/home/eike/repos/master/ma-impl/vipra-ui/app/routes/articles/list.js",
-		"/home/eike/repos/master/ma-impl/vipra-ui/package.json",
-		"/home/eike/repos/master/ma-impl/vipra-ui/node_modules/ember-highcharts/package.json",
-		"/home/eike/repos/master/ma-impl/vm/config/initd-mongod",
-		"/home/eike/repos/master/ma-impl/vm/webapps/vipra-rest/WEB-INF/web.xml",
-		"/core",
-		"/home/eike/repos/master/ma-impl/vm/config/environment",
-		"/home/eike/repos/master/ma-impl/vm/config/initd-tomcat"
+		"/home/eike/repos/master/ma-impl/vipra-ui2/package.json"
 	],
 	"find":
 	{
@@ -935,39 +929,8 @@
 	"groups":
 	[
 		{
-			"selected": 0,
 			"sheets":
 			[
-				{
-					"buffer": 0,
-					"file": "vipra-ui/css/app.less",
-					"semi_transient": false,
-					"settings":
-					{
-						"buffer_size": 1761,
-						"regions":
-						{
-						},
-						"selection":
-						[
-							[
-								492,
-								492
-							]
-						],
-						"settings":
-						{
-							"syntax": "Packages/LESS/LESS.tmLanguage",
-							"tab_size": 2,
-							"translate_tabs_to_spaces": true
-						},
-						"translation.x": -0.0,
-						"translation.y": 102.0,
-						"zoom_level": 1.0
-					},
-					"stack_index": 0,
-					"type": "text"
-				}
 			]
 		}
 	],
@@ -1122,7 +1085,7 @@
 		"selected_items":
 		[
 		],
-		"width": 378.0
+		"width": 604.0
 	},
 	"selected_group": 0,
 	"settings":
diff --git a/vipra-rest/src/main/java/de/vipra/rest/provider/ObjectMapperProvider.java b/vipra-rest/src/main/java/de/vipra/rest/provider/ObjectMapperProvider.java
index 490abad971e2c113513275372512a5e8580eeab5..e78b661e6c1c1a1faa024e7d7f865cfa45f31d28 100644
--- a/vipra-rest/src/main/java/de/vipra/rest/provider/ObjectMapperProvider.java
+++ b/vipra-rest/src/main/java/de/vipra/rest/provider/ObjectMapperProvider.java
@@ -42,7 +42,7 @@ public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
 
 		final ObjectMapper mapper = new ObjectMapper();
 		mapper.enable(SerializationFeature.INDENT_OUTPUT);
-		mapper.setSerializationInclusion(Include.NON_NULL);
+		mapper.setSerializationInclusion(Include.NON_EMPTY);
 		mapper.setDateFormat(new SimpleDateFormat(Constants.DATETIME_FORMAT));
 		mapper.registerModule(module);
 		return mapper;
diff --git a/vipra-rest/src/main/java/de/vipra/rest/resource/ArticleResource.java b/vipra-rest/src/main/java/de/vipra/rest/resource/ArticleResource.java
index a919f92ab5273cf3cad2d0c12783f89bef1724d1..bbea795e308fc7401c946512250caa89a9635944 100644
--- a/vipra-rest/src/main/java/de/vipra/rest/resource/ArticleResource.java
+++ b/vipra-rest/src/main/java/de/vipra/rest/resource/ArticleResource.java
@@ -46,11 +46,11 @@ public class ArticleResource {
 	UriInfo uri;
 
 	final Cache<String, ArticleFull> cache;
-	final DatabaseService<ArticleFull, ObjectId> service;
+	final DatabaseService<ArticleFull, ObjectId> dbArticles;
 
 	public ArticleResource(@Context ServletContext servletContext) throws ConfigException, IOException {
 		Config config = Config.getConfig();
-		service = DatabaseService.getDatabaseService(config, ArticleFull.class);
+		dbArticles = DatabaseService.getDatabaseService(config, ArticleFull.class);
 
 		CacheManager manager = (CacheManager) servletContext.getAttribute("cachemanager");
 		Cache<String, ArticleFull> articleCache = manager.getCache("articlecache", String.class, ArticleFull.class);
@@ -68,16 +68,16 @@ public class ArticleResource {
 		Wrapper<List<ArticleFull>> res = new Wrapper<>();
 
 		if (skip != null && limit != null)
-			res.addPaginationLinks(uri.getAbsolutePath(), skip, limit, service.count());
+			res.addPaginationLinks(uri.getAbsolutePath(), skip, limit, dbArticles.count());
 
 		if (res.hasErrors())
 			return res.badRequest();
 
 		try {
-			List<ArticleFull> articles = service.getMultiple(skip, limit, sortBy, StringUtils.getFields(fields));
+			List<ArticleFull> articles = dbArticles.getMultiple(skip, limit, sortBy, StringUtils.getFields(fields));
 
 			if ((skip != null && skip > 0) || (limit != null && limit > 0))
-				res.addMeta("total", service.count());
+				res.addMeta("total", dbArticles.count());
 			else
 				res.addMeta("total", articles.size());
 
@@ -123,7 +123,7 @@ public class ArticleResource {
 	public Response createArticle(ArticleFull article) {
 		Wrapper<ArticleFull> res;
 		try {
-			article = service.createSingle(article);
+			article = dbArticles.createSingle(article);
 			res = new Wrapper<>(article);
 			URI newUri = new URL(uri.getAbsolutePath().toURL(), article.getId().toString()).toURI();
 			return res.created(newUri);
@@ -140,7 +140,7 @@ public class ArticleResource {
 		Wrapper<ArticleFull> res = new Wrapper<>();
 		long deleted;
 		try {
-			deleted = service.deleteSingle(MongoUtils.objectId(id));
+			deleted = dbArticles.deleteSingle(MongoUtils.objectId(id));
 		} catch (DatabaseException e) {
 			res = new Wrapper<>(new APIError(Response.Status.INTERNAL_SERVER_ERROR, "item could not be deleted",
 					"item could not be created due to an internal server error"));
@@ -168,7 +168,7 @@ public class ArticleResource {
 		ArticleFull article = wrapper.getData();
 		Wrapper<ArticleFull> res = new Wrapper<>();
 		try {
-			service.updateSingle(article);
+			dbArticles.updateSingle(article);
 			cache.put(id, article);
 			return res.ok(article);
 		} catch (DatabaseException e) {
@@ -182,13 +182,13 @@ public class ArticleResource {
 		if (fields == null || fields.length == 0) {
 			ArticleFull article = cache.get(id);
 			if (article == null) {
-				article = service.getSingle(MongoUtils.objectId(id));
+				article = dbArticles.getSingle(MongoUtils.objectId(id));
 				if (article != null)
 					cache.put(id, article);
 			}
 			return article;
 		} else
-			return service.getSingle(MongoUtils.objectId(id), fields);
+			return dbArticles.getSingle(MongoUtils.objectId(id), fields);
 	}
 
 }
diff --git a/vipra-rest/src/main/java/de/vipra/rest/resource/ImportResource.java b/vipra-rest/src/main/java/de/vipra/rest/resource/ImportResource.java
index f67a0b495ad04cc5da865321ec2c8975ff11f90c..a28863956774110a9d55ead6b0496455d4ba4d17 100644
--- a/vipra-rest/src/main/java/de/vipra/rest/resource/ImportResource.java
+++ b/vipra-rest/src/main/java/de/vipra/rest/resource/ImportResource.java
@@ -38,11 +38,11 @@ public class ImportResource {
 	UriInfo uri;
 
 	final Cache<String, Import> cache;
-	final DatabaseService<Import, ObjectId> service;
+	final DatabaseService<Import, ObjectId> dbImports;
 
 	public ImportResource(@Context ServletContext servletContext) throws ConfigException, IOException {
 		Config config = Config.getConfig();
-		service = DatabaseService.getDatabaseService(config, Import.class);
+		dbImports = DatabaseService.getDatabaseService(config, Import.class);
 
 		CacheManager manager = (CacheManager) servletContext.getAttribute("cachemanager");
 		Cache<String, Import> importCache = manager.getCache("importcache", String.class, Import.class);
@@ -59,16 +59,16 @@ public class ImportResource {
 		Wrapper<List<Import>> res = new Wrapper<>();
 
 		if (skip != null && limit != null)
-			res.addPaginationLinks(uri.getAbsolutePath(), skip, limit, service.count());
+			res.addPaginationLinks(uri.getAbsolutePath(), skip, limit, dbImports.count());
 
 		if (res.hasErrors())
 			return Response.status(Response.Status.BAD_REQUEST).entity(res).build();
 
 		try {
-			List<Import> imports = service.getMultiple(skip, limit, sortBy, StringUtils.getFields(fields));
+			List<Import> imports = dbImports.getMultiple(skip, limit, sortBy, StringUtils.getFields(fields));
 
 			if ((skip != null && skip > 0) || (limit != null && limit > 0))
-				res.addMeta("total", service.count());
+				res.addMeta("total", dbImports.count());
 			else
 				res.addMeta("total", imports.size());
 
@@ -85,7 +85,7 @@ public class ImportResource {
 	@Path("latest")
 	public Response getLatestImport(@QueryParam("fields") String fields) {
 		Wrapper<Import> res = new Wrapper<>();
-		List<Import> latestImport = service.getMultiple(0, 1, "date", false, StringUtils.getFields(fields));
+		List<Import> latestImport = dbImports.getMultiple(0, 1, "date", null, false, StringUtils.getFields(fields));
 
 		if (latestImport == null || latestImport.size() != 1) {
 			return res.noContent();
@@ -127,13 +127,13 @@ public class ImportResource {
 		if (fields == null || fields.length == 0) {
 			Import importOp = cache.get(id);
 			if (importOp == null) {
-				importOp = service.getSingle(MongoUtils.objectId(id));
+				importOp = dbImports.getSingle(MongoUtils.objectId(id));
 				if (importOp != null)
 					cache.put(id, importOp);
 			}
 			return importOp;
 		} else
-			return service.getSingle(MongoUtils.objectId(id), fields);
+			return dbImports.getSingle(MongoUtils.objectId(id), fields);
 	}
 
 }
diff --git a/vipra-rest/src/main/java/de/vipra/rest/resource/TopicResource.java b/vipra-rest/src/main/java/de/vipra/rest/resource/TopicResource.java
index 7675508c2462bfd6061c38b1aa99fd891c7b266d..f158154c670a87ff1bdc6154c405d39c8b1a3436 100644
--- a/vipra-rest/src/main/java/de/vipra/rest/resource/TopicResource.java
+++ b/vipra-rest/src/main/java/de/vipra/rest/resource/TopicResource.java
@@ -17,6 +17,8 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 import org.bson.types.ObjectId;
 import org.ehcache.Cache;
 import org.ehcache.CacheManager;
@@ -30,8 +32,10 @@ import de.vipra.util.MongoUtils;
 import de.vipra.util.StringUtils;
 import de.vipra.util.ex.ConfigException;
 import de.vipra.util.ex.DatabaseException;
+import de.vipra.util.model.ArticleFull;
 import de.vipra.util.model.TopicFull;
 import de.vipra.util.service.DatabaseService;
+import de.vipra.util.service.Service.QueryBuilder;
 
 @Path("topics")
 public class TopicResource {
@@ -40,11 +44,15 @@ public class TopicResource {
 	UriInfo uri;
 
 	final Cache<String, TopicFull> cache;
-	final DatabaseService<TopicFull, ObjectId> service;
+	final DatabaseService<TopicFull, ObjectId> dbTopics;
+	final DatabaseService<ArticleFull, ObjectId> dbArticles;
+
+	public static final Logger log = LogManager.getLogger(TopicResource.class);
 
 	public TopicResource(@Context ServletContext servletContext) throws ConfigException, IOException {
 		Config config = Config.getConfig();
-		service = DatabaseService.getDatabaseService(config, TopicFull.class);
+		dbTopics = DatabaseService.getDatabaseService(config, TopicFull.class);
+		dbArticles = DatabaseService.getDatabaseService(config, ArticleFull.class);
 
 		CacheManager manager = (CacheManager) servletContext.getAttribute("cachemanager");
 		Cache<String, TopicFull> topicCache = manager.getCache("topiccache", String.class, TopicFull.class);
@@ -61,16 +69,16 @@ public class TopicResource {
 		Wrapper<List<TopicFull>> res = new Wrapper<>();
 
 		if (skip != null && limit != null)
-			res.addPaginationLinks(uri.getAbsolutePath(), skip, limit, service.count());
+			res.addPaginationLinks(uri.getAbsolutePath(), skip, limit, dbTopics.count());
 
 		if (res.hasErrors())
 			return Response.status(Response.Status.BAD_REQUEST).entity(res).build();
 
 		try {
-			List<TopicFull> topics = service.getMultiple(skip, limit, sortBy, StringUtils.getFields(fields));
+			List<TopicFull> topics = dbTopics.getMultiple(skip, limit, sortBy, StringUtils.getFields(fields));
 
 			if ((skip != null && skip > 0) || (limit != null && limit > 0))
-				res.addMeta("total", service.count());
+				res.addMeta("total", dbTopics.count());
 			else
 				res.addMeta("total", topics.size());
 
@@ -85,7 +93,8 @@ public class TopicResource {
 	@Produces(MediaType.APPLICATION_JSON)
 	@Consumes(MediaType.APPLICATION_JSON)
 	@Path("{id}")
-	public Response getTopic(@PathParam("id") String id, @QueryParam("fields") String fields) {
+	public Response getTopic(@PathParam("id") String id, @QueryParam("fields") String fields)
+			throws ConfigException, IOException {
 		Wrapper<TopicFull> res = new Wrapper<>();
 		if (id == null || id.trim().length() == 0) {
 			res.addError(new APIError(Response.Status.BAD_REQUEST, "ID is empty",
@@ -101,9 +110,12 @@ public class TopicResource {
 			return res.badRequest();
 		}
 
-		if (topic != null)
+		if (topic != null) {
+			List<ArticleFull> articles = dbArticles.getMultiple(QueryBuilder.builder().criteria("topics.topic", topic));
+			topic.setArticles(articles);
+
 			return res.ok(topic);
-		else {
+		} else {
 			res.addError(new APIError(Response.Status.NOT_FOUND, "Resource not found",
 					String.format(Messages.NOT_FOUND, "topic", id)));
 			return res.notFound();
@@ -118,7 +130,7 @@ public class TopicResource {
 		TopicFull topic = wrapper.getData();
 		Wrapper<TopicFull> res = new Wrapper<>();
 		try {
-			service.updateSingle(topic);
+			dbTopics.updateSingle(topic);
 			cache.put(id, topic);
 			return res.ok(topic);
 		} catch (DatabaseException e) {
@@ -132,13 +144,13 @@ public class TopicResource {
 		if (fields == null || fields.length == 0) {
 			TopicFull topic = cache.get(id);
 			if (topic == null) {
-				topic = service.getSingle(MongoUtils.objectId(id));
+				topic = dbTopics.getSingle(MongoUtils.objectId(id));
 				if (topic != null)
 					cache.put(id, topic);
 			}
 			return topic;
 		} else
-			return service.getSingle(MongoUtils.objectId(id), fields);
+			return dbTopics.getSingle(MongoUtils.objectId(id), fields);
 	}
 
 }
diff --git a/vipra-rest/src/main/java/de/vipra/rest/resource/WordResource.java b/vipra-rest/src/main/java/de/vipra/rest/resource/WordResource.java
index b05dbef57884ca03b6ffefb71d4e366303f959cb..c8cb5df41e92327400966cac2deab1f838545f2c 100644
--- a/vipra-rest/src/main/java/de/vipra/rest/resource/WordResource.java
+++ b/vipra-rest/src/main/java/de/vipra/rest/resource/WordResource.java
@@ -16,6 +16,7 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 
+import org.bson.types.ObjectId;
 import org.ehcache.Cache;
 import org.ehcache.CacheManager;
 import org.ehcache.config.CacheConfigurationBuilder;
@@ -26,8 +27,10 @@ import de.vipra.rest.model.Wrapper;
 import de.vipra.util.Config;
 import de.vipra.util.StringUtils;
 import de.vipra.util.ex.ConfigException;
+import de.vipra.util.model.TopicFull;
 import de.vipra.util.model.Word;
 import de.vipra.util.service.DatabaseService;
+import de.vipra.util.service.Service.QueryBuilder;
 
 @Path("words")
 public class WordResource {
@@ -36,11 +39,13 @@ public class WordResource {
 	UriInfo uri;
 
 	final Cache<String, Word> cache;
-	final DatabaseService<Word, String> service;
+	final DatabaseService<Word, String> dbWords;
+	final DatabaseService<TopicFull, ObjectId> dbTopics;
 
 	public WordResource(@Context ServletContext servletContext) throws ConfigException, IOException {
 		Config config = Config.getConfig();
-		service = DatabaseService.getDatabaseService(config, Word.class);
+		dbWords = DatabaseService.getDatabaseService(config, Word.class);
+		dbTopics = DatabaseService.getDatabaseService(config, TopicFull.class);
 
 		CacheManager manager = (CacheManager) servletContext.getAttribute("cachemanager");
 		Cache<String, Word> wordCache = manager.getCache("wordcache", String.class, Word.class);
@@ -57,16 +62,16 @@ public class WordResource {
 		Wrapper<List<Word>> res = new Wrapper<>();
 
 		if (skip != null && limit != null)
-			res.addPaginationLinks(uri.getAbsolutePath(), skip, limit, service.count());
+			res.addPaginationLinks(uri.getAbsolutePath(), skip, limit, dbWords.count());
 
 		if (res.hasErrors())
 			return res.badRequest();
 
 		try {
-			List<Word> words = service.getMultiple(skip, limit, sortBy, StringUtils.getFields(fields));
+			List<Word> words = dbWords.getMultiple(skip, limit, sortBy, StringUtils.getFields(fields));
 
 			if ((skip != null && skip > 0) || (limit != null && limit > 0))
-				res.addMeta("total", service.count());
+				res.addMeta("total", dbWords.count());
 			else
 				res.addMeta("total", words.size());
 
@@ -97,9 +102,13 @@ public class WordResource {
 			return res.badRequest();
 		}
 
-		if (word != null)
+		if (word != null) {
+			List<TopicFull> topics = dbTopics.getMultiple(
+					QueryBuilder.builder().fields(false, "index", "created", "modified").criteria("words.word", word));
+			word.setTopics(topics);
+
 			return res.ok(word);
-		else {
+		} else {
 			String msg = String.format(Messages.NOT_FOUND, "word", id);
 			res.addError(new APIError(Response.Status.NOT_FOUND, "Resource not found", msg));
 			return res.notFound();
@@ -110,13 +119,13 @@ public class WordResource {
 		if (fields == null || fields.length == 0) {
 			Word word = cache.get(id);
 			if (word == null) {
-				word = service.getSingle(id);
+				word = dbWords.getSingle(id);
 				if (word != null)
 					cache.put(id, word);
 			}
 			return word;
 		} else
-			return service.getSingle(id, fields);
+			return dbWords.getSingle(id, fields);
 	}
 
 }
diff --git a/vipra-ui/html/topics/show.html b/vipra-ui/html/topics/show.html
index 45b6595814b7f04d7ce8788b4e79e849f4e983c8..38d1648ede547bad61d2c42d76e7ef2003194cba 100644
--- a/vipra-ui/html/topics/show.html
+++ b/vipra-ui/html/topics/show.html
@@ -21,7 +21,15 @@
   </tbody>
 </table>
 
-<h4>Words</h4>
+<h3>Articles</h3>
+
+<ul class="list-unstyled">
+  <li ng-repeat="article in ::topic.articles">
+    <article-link article="article"/>
+  </li>
+</ul>
+
+<h3>Words</h3>
 
 <table class="table table-bordered table-condensed">
   <thead>
diff --git a/vipra-ui/html/words/show.html b/vipra-ui/html/words/show.html
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1ab0373b9d80526fe1895bf5c55481f6a77c62a6 100644
--- a/vipra-ui/html/words/show.html
+++ b/vipra-ui/html/words/show.html
@@ -0,0 +1,18 @@
+<h1 ng-bind="::word.id"></h1>
+
+<table class="table table-bordered table-condensed">
+  <tbody>
+    <tr>
+      <th>Created</th>
+      <td ng-bind="::word.created"></td>
+    </tr>
+  </tbody>
+</table>
+
+<h3>Topics</h3>
+
+<ul class="list-unstyled">
+  <li ng-repeat="topic in ::word.topics">
+    <topic-link topic="topic"/>
+  </li>
+</ul>
\ No newline at end of file
diff --git a/vipra-ui/js/controllers.js b/vipra-ui/js/controllers.js
index fe19b5f01344aa6aba4bba10ac6baa50582a5655..0eabe9f4852207196d67f05fdef03aa232bcf04f 100644
--- a/vipra-ui/js/controllers.js
+++ b/vipra-ui/js/controllers.js
@@ -127,6 +127,7 @@
 
     WordFactory.get({id: $stateParams.id}, function(response) {
       $scope.word = response.data;
+      $scope.word.created = formatDateTime($scope.word.created);
       $scope.wordMeta = response.meta;
       $scope.queryTime = response.$queryTime;
     });
diff --git a/vipra-ui/js/directives.js b/vipra-ui/js/directives.js
index a5681be6b7a90c2135129d2b1d5feec91e8b17d0..46bff5302cc34918a871bd9d4d50eeb470944c62 100644
--- a/vipra-ui/js/directives.js
+++ b/vipra-ui/js/directives.js
@@ -18,6 +18,18 @@
     }
   });
 
+  app.directive('articleLink', function() {
+    return {
+      scope: {
+        article: '='
+      },
+      restrict: 'E',
+      replace: true,
+      transclude: true,
+      template: '<a class="article-link" ui-sref="articles.show({id:article.id})"><span ng-bind="article.title"></span><ng-transclude/></a>'
+    }
+  });
+
   app.directive('queryTime', function() {
     return {
       restrict: 'E',
diff --git a/vipra-util/src/main/java/de/vipra/util/Pair.java b/vipra-util/src/main/java/de/vipra/util/Pair.java
new file mode 100644
index 0000000000000000000000000000000000000000..23c2e378094845c731aa2a7bbf90b1caa50ac8eb
--- /dev/null
+++ b/vipra-util/src/main/java/de/vipra/util/Pair.java
@@ -0,0 +1,35 @@
+package de.vipra.util;
+
+public class Pair<X, Y> {
+
+	private X x;
+	private Y y;
+
+	public Pair() {}
+
+	public Pair(X x, Y y) {
+		this.x = x;
+		this.y = y;
+	}
+
+	public X x() {
+		return x;
+	}
+
+	public void setX(X x) {
+		this.x = x;
+	}
+
+	public Y y() {
+		return y;
+	}
+
+	public void setY(Y y) {
+		this.y = y;
+	}
+
+	public static <X, Y> Pair<X, Y> pair(X x, Y y) {
+		return new Pair<>(x, y);
+	}
+
+}
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 4449f5b26eeceabe4d2aec28c13d85b039f8148e..afa7d461397c8343837b55604b91988cf2cb3475 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
@@ -11,20 +11,20 @@ public class ArticleStats implements Serializable {
 
 	private static final long serialVersionUID = -4712841724990200627L;
 
-	private long wordCount;
+	private Long wordCount;
 
-	public long getWordCount() {
+	public Long getWordCount() {
 		return wordCount;
 	}
 
-	public void setWordCount(long wordCount) {
+	public void setWordCount(Long wordCount) {
 		this.wordCount = wordCount;
 	}
 
 	public static ArticleStats generateFromText(final String text, final WordMap wordMap) {
 		ArticleStats stats = new ArticleStats();
 		String[] words = text.split("\\s+");
-		stats.setWordCount(words.length);
+		stats.setWordCount((long) words.length);
 		return stats;
 	}
 
diff --git a/vipra-util/src/main/java/de/vipra/util/model/Import.java b/vipra-util/src/main/java/de/vipra/util/model/Import.java
index a89697388f89d02f78b042d0dfa14e795ad6d4ff..4ed9ce315b851c43465e814fa534c59c0bc51488 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/Import.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/Import.java
@@ -24,25 +24,25 @@ public class Import implements Model<ObjectId>, Serializable {
 
 	private Date date;
 
-	private long duration;
+	private Long duration;
 
 	@QueryIgnore(multi = true)
 	@Reference(ignoreMissing = true)
 	private List<Article> articles;
 
-	private int articlesCount;
+	private Integer articlesCount;
 
 	@QueryIgnore(multi = true)
 	@Reference(ignoreMissing = true)
 	private List<Topic> topics;
 
-	private int topicsCount;
+	private Integer topicsCount;
 
 	@QueryIgnore(multi = true)
 	@Reference(ignoreMissing = true)
 	private List<Word> words;
 
-	private int wordsCount;
+	private Integer wordsCount;
 
 	@Override
 	public ObjectId getId() {
@@ -62,11 +62,11 @@ public class Import implements Model<ObjectId>, Serializable {
 		this.date = date;
 	}
 
-	public long getDuration() {
+	public Long getDuration() {
 		return duration;
 	}
 
-	public void setDuration(long duration) {
+	public void setDuration(Long duration) {
 		this.duration = duration;
 	}
 
@@ -80,7 +80,7 @@ public class Import implements Model<ObjectId>, Serializable {
 			articlesCount = articles.size();
 	}
 
-	public int getArticlesCount() {
+	public Integer getArticlesCount() {
 		return articlesCount;
 	}
 
@@ -94,7 +94,7 @@ public class Import implements Model<ObjectId>, Serializable {
 			topicsCount = topics.size();
 	}
 
-	public int getTopicsCount() {
+	public Integer getTopicsCount() {
 		return topicsCount;
 	}
 
@@ -108,7 +108,7 @@ public class Import implements Model<ObjectId>, Serializable {
 			wordsCount = words.size();
 	}
 
-	public int getWordsCount() {
+	public Integer getWordsCount() {
 		return wordsCount;
 	}
 
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 63750c08c94d55f69d34bd5bdc893da663359fbd..f004d83788a70049a290eb3049588d1d8346d6c0 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
@@ -10,6 +10,7 @@ import org.mongodb.morphia.annotations.Embedded;
 import org.mongodb.morphia.annotations.Entity;
 import org.mongodb.morphia.annotations.Id;
 import org.mongodb.morphia.annotations.PrePersist;
+import org.mongodb.morphia.annotations.Transient;
 
 import de.vipra.util.Constants;
 import de.vipra.util.MongoUtils;
@@ -25,12 +26,15 @@ public class TopicFull implements Model<ObjectId>, Serializable {
 
 	private String name;
 
-	private int index;
+	private Integer index;
 
 	@Embedded
 	@QueryIgnore(multi = true)
 	private List<TopicWord> words;
 
+	@Transient
+	private List<ArticleFull> articles;
+
 	private Date created;
 
 	private Date modified;
@@ -57,11 +61,11 @@ public class TopicFull implements Model<ObjectId>, Serializable {
 		this.name = name;
 	}
 
-	public int getIndex() {
+	public Integer getIndex() {
 		return index;
 	}
 
-	public void setIndex(int index) {
+	public void setIndex(Integer index) {
 		this.index = index;
 	}
 
@@ -73,6 +77,14 @@ public class TopicFull implements Model<ObjectId>, Serializable {
 		this.words = topicWords;
 	}
 
+	public List<ArticleFull> getArticles() {
+		return articles;
+	}
+
+	public void setArticles(List<ArticleFull> articles) {
+		this.articles = articles;
+	}
+
 	public Date getCreated() {
 		return created;
 	}
diff --git a/vipra-util/src/main/java/de/vipra/util/model/TopicRef.java b/vipra-util/src/main/java/de/vipra/util/model/TopicRef.java
index 920abdce7074e73c8c4dd3e99cd33087e5a8ee17..c5de02e6b750df507e7d8d0f9b15bc2eed307f21 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/TopicRef.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/TopicRef.java
@@ -14,7 +14,7 @@ public class TopicRef implements Comparable<TopicRef>, Serializable {
 	private String topicIndex;
 	@Reference(ignoreMissing = true)
 	private Topic topic;
-	private int count;
+	private Integer count;
 
 	public String getTopicIndex() {
 		return topicIndex;
@@ -24,11 +24,11 @@ public class TopicRef implements Comparable<TopicRef>, Serializable {
 		this.topicIndex = index;
 	}
 
-	public int getCount() {
+	public Integer getCount() {
 		return count;
 	}
 
-	public void setCount(int count) {
+	public void setCount(Integer count) {
 		this.count = count;
 	}
 
diff --git a/vipra-util/src/main/java/de/vipra/util/model/TopicWord.java b/vipra-util/src/main/java/de/vipra/util/model/TopicWord.java
index abed0a8abfbf7641b852e6ae2a0f9728354b6e32..bd79ed2b418461b4f33432d7537c765e479e80a1 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/TopicWord.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/TopicWord.java
@@ -20,11 +20,11 @@ public class TopicWord implements Comparable<TopicWord>, Serializable {
 	@JsonProperty("word")
 	private String wordString;
 
-	private double likeliness;
+	private Double likeliness;
 
 	public TopicWord() {}
 
-	public TopicWord(Word word, double likeliness) {
+	public TopicWord(Word word, Double likeliness) {
 		this.word = word;
 		this.likeliness = likeliness;
 	}
@@ -41,11 +41,11 @@ public class TopicWord implements Comparable<TopicWord>, Serializable {
 		return wordString;
 	}
 
-	public double getLikeliness() {
+	public Double getLikeliness() {
 		return likeliness;
 	}
 
-	public void setLikeliness(double likeliness) {
+	public void setLikeliness(Double likeliness) {
 		this.likeliness = likeliness;
 	}
 
diff --git a/vipra-util/src/main/java/de/vipra/util/model/Word.java b/vipra-util/src/main/java/de/vipra/util/model/Word.java
index 06758a80987ac6a13e99019ea9846a74aa57e24c..7b5c4ed688ebfd12b1facebe2d666648cdfed9c6 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/Word.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/Word.java
@@ -2,6 +2,7 @@ package de.vipra.util.model;
 
 import java.io.Serializable;
 import java.util.Date;
+import java.util.List;
 
 import org.mongodb.morphia.annotations.Entity;
 import org.mongodb.morphia.annotations.Id;
@@ -36,6 +37,9 @@ public class Word implements Model<String>, Serializable {
 	@JsonIgnore
 	private String word;
 
+	@Transient
+	private List<TopicFull> topics;
+
 	@QueryIgnore(multi = true)
 	private Date created;
 
@@ -73,6 +77,14 @@ public class Word implements Model<String>, Serializable {
 		this.id = word;
 	}
 
+	public List<TopicFull> getTopics() {
+		return topics;
+	}
+
+	public void setTopics(List<TopicFull> topics) {
+		this.topics = topics;
+	}
+
 	public boolean isCreated() {
 		return isCreated;
 	}
diff --git a/vipra-util/src/main/java/de/vipra/util/service/DatabaseService.java b/vipra-util/src/main/java/de/vipra/util/service/DatabaseService.java
index da1e780a62b2f21dece1eecce1d3c999c842639a..692dc93efe156cde80e707079e73bf05a4614aab 100644
--- a/vipra-util/src/main/java/de/vipra/util/service/DatabaseService.java
+++ b/vipra-util/src/main/java/de/vipra/util/service/DatabaseService.java
@@ -13,6 +13,7 @@ import org.mongodb.morphia.query.Query;
 import de.vipra.util.Config;
 import de.vipra.util.ListUtils;
 import de.vipra.util.Mongo;
+import de.vipra.util.Pair;
 import de.vipra.util.an.QueryIgnore;
 import de.vipra.util.ex.ConfigException;
 import de.vipra.util.ex.DatabaseException;
@@ -57,29 +58,56 @@ public class DatabaseService<Type extends Model<IdType>, IdType> implements Serv
 
 	@Override
 	public List<Type> getMultiple(Integer skip, Integer limit, String sortBy, String... fields) {
-		return getMultiple(skip, limit, sortBy, true, fields);
+		return getMultiple(QueryBuilder.builder().skip(skip).limit(limit).sortBy(sortBy).fields(true, fields));
 	}
 
 	@Override
-	public List<Type> getMultiple(Integer skip, Integer limit, String sortBy, boolean defaultIgnore, String... fields) {
+	public List<Type> getMultiple(Integer skip, Integer limit, String sortBy, Pair<String, Object> criteria,
+			String... fields) {
+		return getMultiple(
+				QueryBuilder.builder().skip(skip).limit(limit).sortBy(sortBy).criteria(criteria).fields(true, fields));
+	}
+
+	@Override
+	public List<Type> getMultiple(Integer skip, Integer limit, String sortBy, Pair<String, Object> criteria,
+			boolean defaultIgnore, String... fields) {
+		return getMultiple(QueryBuilder.builder().skip(skip).limit(limit).sortBy(sortBy).fields(true, fields)
+				.criteria(criteria).defaultIgnore(defaultIgnore));
+	}
+
+	@Override
+	public List<Type> getMultiple(QueryBuilder builder) {
 		Query<Type> q = datastore.createQuery(clazz);
-		if (skip != null && skip > 0)
-			q.offset(skip);
-		if (limit != null && limit > 0)
-			q.limit(limit);
-		if (sortBy != null)
-			q.order(sortBy);
-		if (fields != null && fields.length > 0)
-			q.retrievedFields(true, setMinus(fields, ignoredFieldsMulti));
-		else if (defaultIgnore && ignoredFieldsMulti.length > 0)
+		if (builder.getSkip() != null && builder.getSkip() > 0)
+			q.offset(builder.getSkip());
+		if (builder.getLimit() != null && builder.getLimit() > 0)
+			q.limit(builder.getLimit());
+		if (builder.getSortBy() != null)
+			q.order(builder.getSortBy());
+		if (builder.getCriteria() != null)
+			for (Pair<String, Object> criteria : builder.getCriteria())
+				q.field(criteria.x()).equal(criteria.y());
+		if (builder.getFields() != null) {
+			String[] fields = builder.getFields();
+			if (builder.isInclude()) {
+				if (builder.isDefaultIgnore() && ignoredFieldsMulti.length > 0)
+					fields = setMinus(fields, ignoredFieldsMulti);
+				q.retrievedFields(true, fields);
+			} else {
+				if (builder.isDefaultIgnore() && ignoredFieldsMulti.length > 0)
+					fields = setPlus(fields, ignoredFieldsMulti);
+				q.retrievedFields(false, fields);
+			}
+		} else if (ignoredFieldsMulti.length > 0) {
 			q.retrievedFields(false, ignoredFieldsMulti);
+		}
 		List<Type> list = q.asList();
 		return list;
 	}
 
 	@Override
 	public List<Type> getAll(String... fields) {
-		return getMultiple(null, null, null, fields);
+		return getMultiple(null, null, null, null, fields);
 	}
 
 	@Override
@@ -131,4 +159,14 @@ public class DatabaseService<Type extends Model<IdType>, IdType> implements Serv
 		return a;
 	}
 
+	private String[] setPlus(String[] a, String[] b) {
+		if (a != null && b != null) {
+			Set<String> sa = new HashSet<>(Arrays.asList(a));
+			Set<String> sb = new HashSet<>(Arrays.asList(b));
+			sa.addAll(sb);
+			return sa.toArray(new String[sa.size()]);
+		}
+		return a;
+	}
+
 }
diff --git a/vipra-util/src/main/java/de/vipra/util/service/Service.java b/vipra-util/src/main/java/de/vipra/util/service/Service.java
index 3282b1728c499bd70178ff7326d073fe8ecf1ed6..dbdc07eedbdeb76c438f80f6a6a0977eaa86da70 100644
--- a/vipra-util/src/main/java/de/vipra/util/service/Service.java
+++ b/vipra-util/src/main/java/de/vipra/util/service/Service.java
@@ -1,7 +1,9 @@
 package de.vipra.util.service;
 
+import java.util.ArrayList;
 import java.util.List;
 
+import de.vipra.util.Pair;
 import de.vipra.util.model.Model;
 
 public interface Service<Type extends Model<IdType>, IdType, E extends Exception> {
@@ -10,9 +12,14 @@ public interface Service<Type extends Model<IdType>, IdType, E extends Exception
 
 	List<Type> getMultiple(Integer skip, Integer limit, String sortBy, String... fields) throws E;
 
-	List<Type> getMultiple(Integer skip, Integer limit, String sortBy, boolean noDefaultIgnore, String... fields)
+	List<Type> getMultiple(Integer skip, Integer limit, String sortBy, Pair<String, Object> criteria, String... fields)
 			throws E;
 
+	List<Type> getMultiple(Integer skip, Integer limit, String sortBy, Pair<String, Object> criteria,
+			boolean noDefaultIgnore, String... fields) throws E;
+
+	List<Type> getMultiple(QueryBuilder builder) throws E;
+
 	List<Type> getAll(String... fields) throws E;
 
 	Type createSingle(Type t) throws E;
@@ -27,4 +34,88 @@ public interface Service<Type extends Model<IdType>, IdType, E extends Exception
 
 	long count() throws E;
 
+	public static class QueryBuilder {
+
+		private Integer skip;
+		private Integer limit;
+		private String sortBy;
+		private List<Pair<String, Object>> criteria;
+		private String[] fields;
+		private boolean include;
+		private boolean defaultIgnore = true;
+
+		private QueryBuilder() {}
+
+		public static QueryBuilder builder() {
+			return new QueryBuilder();
+		}
+
+		public QueryBuilder skip(Integer skip) {
+			this.skip = skip;
+			return this;
+		}
+
+		public QueryBuilder limit(Integer limit) {
+			this.limit = limit;
+			return this;
+		}
+
+		public QueryBuilder sortBy(String sortBy) {
+			this.sortBy = sortBy;
+			return this;
+		}
+
+		public QueryBuilder criteria(String field, Object value) {
+			return criteria(Pair.pair(field, value));
+		}
+
+		public QueryBuilder criteria(Pair<String, Object> pair) {
+			if (criteria == null) {
+				criteria = new ArrayList<>();
+			}
+			criteria.add(pair);
+			return this;
+		}
+
+		public QueryBuilder fields(boolean include, String... strings) {
+			this.include = include;
+			this.fields = strings;
+			return this;
+		}
+
+		public QueryBuilder defaultIgnore(boolean defaultIgnore) {
+			this.defaultIgnore = defaultIgnore;
+			return this;
+		}
+
+		public Integer getSkip() {
+			return skip;
+		}
+
+		public Integer getLimit() {
+			return limit;
+		}
+
+		public String getSortBy() {
+			return sortBy;
+		}
+
+		public List<Pair<String, Object>> getCriteria() {
+			return criteria;
+		}
+
+		public boolean isInclude() {
+			return include;
+		}
+
+		public String[] getFields() {
+			return fields;
+		}
+
+		public boolean isDefaultIgnore() {
+			return defaultIgnore;
+		}
+
+	}
+
 }