diff --git a/vipra-rest/src/main/java/de/vipra/rest/CacheAdapter.java b/vipra-rest/src/main/java/de/vipra/rest/CacheAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..2e48b3341aa72d46a73e325a631ad41244473f30 --- /dev/null +++ b/vipra-rest/src/main/java/de/vipra/rest/CacheAdapter.java @@ -0,0 +1,138 @@ +package de.vipra.rest; + +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.ehcache.Cache; +import org.ehcache.CacheManager; +import org.ehcache.config.CacheConfiguration; +import org.ehcache.config.CacheConfigurationBuilder; +import org.ehcache.config.CacheRuntimeConfiguration; +import org.ehcache.exceptions.BulkCacheLoadingException; +import org.ehcache.exceptions.BulkCacheWritingException; +import org.ehcache.exceptions.CacheLoadingException; +import org.ehcache.exceptions.CacheWritingException; + +import de.vipra.util.AbstractCache; + +public class CacheAdapter<T, U> implements AbstractCache<T, U> { + + public static class NullCache<T, U> implements Cache<T, U> { + + @Override + public Iterator<org.ehcache.Cache.Entry<T, U>> iterator() { + return null; + } + + @Override + public U get(T key) throws CacheLoadingException { + return null; + } + + @Override + public void put(T key, U value) throws CacheWritingException {} + + @Override + public boolean containsKey(T key) { + return false; + } + + @Override + public void remove(T key) throws CacheWritingException {} + + @Override + public Map<T, U> getAll(Set<? extends T> keys) throws BulkCacheLoadingException { + return null; + } + + @Override + public void putAll(Map<? extends T, ? extends U> entries) throws BulkCacheWritingException {} + + @Override + public void removeAll(Set<? extends T> keys) throws BulkCacheWritingException {} + + @Override + public void clear() {} + + @Override + public U putIfAbsent(T key, U value) throws CacheLoadingException, CacheWritingException { + return null; + } + + @Override + public boolean remove(T key, U value) throws CacheWritingException { + return false; + } + + @Override + public U replace(T key, U value) throws CacheLoadingException, CacheWritingException { + return null; + } + + @Override + public boolean replace(T key, U oldValue, U newValue) throws CacheLoadingException, CacheWritingException { + return false; + } + + @Override + public CacheRuntimeConfiguration<T, U> getRuntimeConfiguration() { + return null; + } + + } + + public static final Logger log = LogManager.getLogger(CacheAdapter.class); + + private final Cache<T, U> cache; + + public CacheAdapter(Cache<T, U> cache) { + this.cache = cache; + } + + public CacheAdapter(CacheManager manager, String name, Class<T> keyClass, Class<U> valueClass) { + Cache<T, U> cache = manager.getCache(name, keyClass, valueClass); + if (cache == null) { + CacheConfiguration<T, U> config = CacheConfigurationBuilder.newCacheConfigurationBuilder() + .buildConfig(keyClass, valueClass); + try { + cache = manager.createCache(name, config); + } catch (IllegalArgumentException e) { + cache = manager.getCache(name, keyClass, valueClass); + } + } + if (cache == null) { + log.error("could not create cache, not using cache"); + cache = new NullCache<>(); + } + this.cache = cache; + } + + @Override + public U get(T t) { + return cache.get(t); + } + + @Override + public void put(T t, U u) { + cache.put(t, u); + } + + @Override + public void remove(T t) { + cache.remove(t); + } + + @Override + public boolean contains(T t) { + return cache.containsKey(t); + } + + @Override + public void clear() { + cache.clear(); + } + +} 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 bbea795e308fc7401c946512250caa89a9635944..8a02e335dc073e977baa5fa51f141ce69352e93c 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 @@ -24,10 +24,9 @@ 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; +import de.vipra.rest.CacheAdapter; import de.vipra.rest.Messages; import de.vipra.rest.model.APIError; import de.vipra.rest.model.Wrapper; @@ -45,7 +44,6 @@ public class ArticleResource { @Context UriInfo uri; - final Cache<String, ArticleFull> cache; final DatabaseService<ArticleFull, ObjectId> dbArticles; public ArticleResource(@Context ServletContext servletContext) throws ConfigException, IOException { @@ -53,11 +51,9 @@ public class ArticleResource { dbArticles = DatabaseService.getDatabaseService(config, ArticleFull.class); CacheManager manager = (CacheManager) servletContext.getAttribute("cachemanager"); - Cache<String, ArticleFull> articleCache = manager.getCache("articlecache", String.class, ArticleFull.class); - if (articleCache == null) - articleCache = manager.createCache("articlecache", CacheConfigurationBuilder.newCacheConfigurationBuilder() - .buildConfig(String.class, ArticleFull.class)); - this.cache = articleCache; + CacheAdapter<ObjectId, ArticleFull> cache = new CacheAdapter<>(manager, "articlecacle", ObjectId.class, + ArticleFull.class); + dbArticles.withCache(cache); } @GET @@ -83,6 +79,7 @@ public class ArticleResource { return res.ok(articles); } catch (Exception e) { + e.printStackTrace(); res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage())); return res.badRequest(); } @@ -102,8 +99,9 @@ public class ArticleResource { ArticleFull article; try { - article = getSingle(id, StringUtils.getFields(fields)); + article = dbArticles.getSingle(MongoUtils.objectId(id), StringUtils.getFields(fields)); } catch (Exception e) { + e.printStackTrace(); res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage())); return res.badRequest(); } @@ -128,6 +126,7 @@ public class ArticleResource { URI newUri = new URL(uri.getAbsolutePath().toURL(), article.getId().toString()).toURI(); return res.created(newUri); } catch (DatabaseException | MalformedURLException | URISyntaxException e) { + e.printStackTrace(); res = new Wrapper<>(new APIError(Response.Status.INTERNAL_SERVER_ERROR, "item could not be created", "item could not be created due to an internal server error")); return res.serverError(); @@ -142,11 +141,11 @@ public class ArticleResource { try { deleted = dbArticles.deleteSingle(MongoUtils.objectId(id)); } catch (DatabaseException e) { + e.printStackTrace(); 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")); return res.serverError(); } - cache.remove(id); int del = deleted > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) deleted; switch (del) { case 0: @@ -169,26 +168,13 @@ public class ArticleResource { Wrapper<ArticleFull> res = new Wrapper<>(); try { dbArticles.updateSingle(article); - cache.put(id, article); return res.ok(article); } catch (DatabaseException e) { + e.printStackTrace(); res = new Wrapper<>(new APIError(Response.Status.INTERNAL_SERVER_ERROR, "item could not be updated", "item could not be updated due to an internal server error")); return res.serverError(); } } - private ArticleFull getSingle(String id, String[] fields) { - if (fields == null || fields.length == 0) { - ArticleFull article = cache.get(id); - if (article == null) { - article = dbArticles.getSingle(MongoUtils.objectId(id)); - if (article != null) - cache.put(id, article); - } - return article; - } else - return dbArticles.getSingle(MongoUtils.objectId(id), fields); - } - } diff --git a/vipra-rest/src/main/java/de/vipra/rest/resource/SearchResource.java b/vipra-rest/src/main/java/de/vipra/rest/resource/SearchResource.java index 49f27b9d0f090c9fe9bc7d7703b68e46cbb750d7..e034b7a9768e4e33219de6ad54360f9637619578 100644 --- a/vipra-rest/src/main/java/de/vipra/rest/resource/SearchResource.java +++ b/vipra-rest/src/main/java/de/vipra/rest/resource/SearchResource.java @@ -66,6 +66,7 @@ public class SearchResource { response = client.prepareSearch("articles").setQuery(QueryBuilders.multiMatchQuery(query, "_all")) .setFrom(skip).setSize(limit).execute().actionGet(); } catch (Exception e) { + e.printStackTrace(); res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage())); return res.badRequest(); } 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 f158154c670a87ff1bdc6154c405d39c8b1a3436..21bf8c2c19b48f9d85c26456ef682f58670451a0 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 @@ -20,10 +20,9 @@ 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; -import org.ehcache.config.CacheConfigurationBuilder; +import de.vipra.rest.CacheAdapter; import de.vipra.rest.Messages; import de.vipra.rest.model.APIError; import de.vipra.rest.model.Wrapper; @@ -43,7 +42,6 @@ public class TopicResource { @Context UriInfo uri; - final Cache<String, TopicFull> cache; final DatabaseService<TopicFull, ObjectId> dbTopics; final DatabaseService<ArticleFull, ObjectId> dbArticles; @@ -55,11 +53,9 @@ public class TopicResource { dbArticles = DatabaseService.getDatabaseService(config, ArticleFull.class); CacheManager manager = (CacheManager) servletContext.getAttribute("cachemanager"); - Cache<String, TopicFull> topicCache = manager.getCache("topiccache", String.class, TopicFull.class); - if (topicCache == null) - topicCache = manager.createCache("topiccache", CacheConfigurationBuilder.newCacheConfigurationBuilder() - .buildConfig(String.class, TopicFull.class)); - this.cache = topicCache; + CacheAdapter<ObjectId, TopicFull> cache = new CacheAdapter<>(manager, "topiccache", ObjectId.class, + TopicFull.class); + dbTopics.withCache(cache); } @GET @@ -84,6 +80,7 @@ public class TopicResource { return res.ok(topics); } catch (Exception e) { + e.printStackTrace(); res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage())); return Response.status(Response.Status.BAD_REQUEST).entity(res).build(); } @@ -104,8 +101,9 @@ public class TopicResource { TopicFull topic; try { - topic = getSingle(id, StringUtils.getFields(fields)); + topic = dbTopics.getSingle(MongoUtils.objectId(id), StringUtils.getFields(fields)); } catch (Exception e) { + e.printStackTrace(); res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage())); return res.badRequest(); } @@ -131,26 +129,13 @@ public class TopicResource { Wrapper<TopicFull> res = new Wrapper<>(); try { dbTopics.updateSingle(topic); - cache.put(id, topic); return res.ok(topic); } catch (DatabaseException e) { + e.printStackTrace(); res = new Wrapper<>(new APIError(Response.Status.INTERNAL_SERVER_ERROR, "item could not be updated", "item could not be updated due to an internal server error")); return res.serverError(); } } - private TopicFull getSingle(String id, String[] fields) { - if (fields == null || fields.length == 0) { - TopicFull topic = cache.get(id); - if (topic == null) { - topic = dbTopics.getSingle(MongoUtils.objectId(id)); - if (topic != null) - cache.put(id, topic); - } - return topic; - } else - 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 c8cb5df41e92327400966cac2deab1f838545f2c..46777bc0e03696a765ffac3697755a1b626ceb01 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 @@ -17,10 +17,9 @@ 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; +import de.vipra.rest.CacheAdapter; import de.vipra.rest.Messages; import de.vipra.rest.model.APIError; import de.vipra.rest.model.Wrapper; @@ -38,7 +37,6 @@ public class WordResource { @Context UriInfo uri; - final Cache<String, Word> cache; final DatabaseService<Word, String> dbWords; final DatabaseService<TopicFull, ObjectId> dbTopics; @@ -48,11 +46,8 @@ public class WordResource { dbTopics = DatabaseService.getDatabaseService(config, TopicFull.class); CacheManager manager = (CacheManager) servletContext.getAttribute("cachemanager"); - Cache<String, Word> wordCache = manager.getCache("wordcache", String.class, Word.class); - if (wordCache == null) - wordCache = manager.createCache("wordcache", - CacheConfigurationBuilder.newCacheConfigurationBuilder().buildConfig(String.class, Word.class)); - this.cache = wordCache; + CacheAdapter<String, Word> cache = new CacheAdapter<>(manager, "wordcache", String.class, Word.class); + dbWords.withCache(cache); } @GET @@ -77,6 +72,7 @@ public class WordResource { return res.ok(words); } catch (Exception e) { + e.printStackTrace(); res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage())); return res.badRequest(); } @@ -96,8 +92,9 @@ public class WordResource { Word word; try { - word = getSingle(id, StringUtils.getFields(fields)); + word = dbWords.getSingle(id, StringUtils.getFields(fields)); } catch (Exception e) { + e.printStackTrace(); res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage())); return res.badRequest(); } @@ -115,17 +112,4 @@ public class WordResource { } } - private Word getSingle(String id, String[] fields) { - if (fields == null || fields.length == 0) { - Word word = cache.get(id); - if (word == null) { - word = dbWords.getSingle(id); - if (word != null) - cache.put(id, word); - } - return word; - } else - return dbWords.getSingle(id, fields); - } - } diff --git a/vipra-ui/app/html/articles/index.html b/vipra-ui/app/html/articles/index.html index cece9c1853b073d5c5a442c3fa5296b27fa989f8..cbb03f8f0a6eb01fd19bd9aaaf3e8c3ef9a94034 100644 --- a/vipra-ui/app/html/articles/index.html +++ b/vipra-ui/app/html/articles/index.html @@ -2,10 +2,10 @@ Found <span ng-bind="articlesMeta.total"></span> articles in the database <query-time/>. </p> -<ul class="list-unstyled"> +<ul class="dashed"> <li ng-repeat="article in articles"> <a ui-sref="articles.show({id: article.id})" ng-bind="::article.title"></a> </li> </ul> -<pagination total="articlesMeta.total" page="page" limit="limit" change="change"/> \ No newline at end of file +<pagination total="articlesMeta.total" page="page" limit="limit" change="changePage"/> \ No newline at end of file diff --git a/vipra-ui/app/html/index.html b/vipra-ui/app/html/index.html index 336e7d2f348a602b8c83c30c000aec72297eac34..7fb2f6a650f8505075b622fddc90a3816c046f6f 100644 --- a/vipra-ui/app/html/index.html +++ b/vipra-ui/app/html/index.html @@ -43,7 +43,7 @@ <div class="text-center" ng-show="searching"> Searching... </div> - <div class="col-md-12" ng-show="!searching && search && searchResults.length == 0"> + <div class="col-md-12" ng-show="!searching && search && (!searchResults || searchResults.length == 0)"> <h4>No Results <query-time/></h4> </div> <div class="col-md-12" ng-show="searchResults.length > 0"> diff --git a/vipra-ui/app/html/modal.html b/vipra-ui/app/html/modal.html index 12a50c29e2c0b08c95d79201e6e748ba2424dd4e..e441d0bf66c3142a7db352366f576f175590b6bf 100644 --- a/vipra-ui/app/html/modal.html +++ b/vipra-ui/app/html/modal.html @@ -1,14 +1,10 @@ <div class="modal-header"> <h3 class="modal-title">I'm a modal!</h3> </div> + <div class="modal-body"> - <ul> - <li ng-repeat="item in items"> - <a href="#" ng-click="$event.preventDefault(); selected.item = item">{{ item }}</a> - </li> - </ul> - Selected: <b>{{ selected.item }}</b> </div> + <div class="modal-footer"> <button class="btn btn-primary" type="button" ng-click="ok()">OK</button> <button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button> diff --git a/vipra-ui/app/html/topics/index.html b/vipra-ui/app/html/topics/index.html index b9b9cf85b040643802770ac17f8c657777e643de..fa32725749cf67c7a1b940a670e1e54363d7d421 100644 --- a/vipra-ui/app/html/topics/index.html +++ b/vipra-ui/app/html/topics/index.html @@ -2,10 +2,10 @@ Found <span ng-bind="topicsMeta.total"></span> topics in the database <query-time/>. </p> -<ul class="list-unstyled"> +<ul class="dashed"> <li ng-repeat="topic in topics"> <a ui-sref="topics.show({id: topic.id})">{{topic.name}}</a> </li> </ul> -<pagination total="topicsMeta.total" page="page" limit="limit" change="change"/> \ No newline at end of file +<pagination total="topicsMeta.total" page="page" limit="limit" change="changePage"/> \ No newline at end of file diff --git a/vipra-ui/app/html/topics/show.html b/vipra-ui/app/html/topics/show.html index a8923889203414a81279a0669695e9db7c83d055..566856a5f93a3ecdc9b2356dfc324763d94270be 100644 --- a/vipra-ui/app/html/topics/show.html +++ b/vipra-ui/app/html/topics/show.html @@ -29,7 +29,7 @@ <h3>Articles <hide-link target="#articles"/></h3> -<ul class="list-unstyled" id="articles"> +<ul class="dashed"> <li ng-repeat="article in ::topic.articles"> <article-link article="article"/> </li> diff --git a/vipra-ui/app/html/words/index.html b/vipra-ui/app/html/words/index.html index 90832f5d8e7446f1eacd1103714260208e7fce87..631b2dc2c7ebea7d5a6cda3b4e076d768126113d 100644 --- a/vipra-ui/app/html/words/index.html +++ b/vipra-ui/app/html/words/index.html @@ -26,4 +26,4 @@ </div> </div> -<pagination total="wordsMeta.total" page="page" limit="limit" change="change"/> \ No newline at end of file +<pagination total="wordsMeta.total" page="page" limit="limit" change="changePage"/> \ No newline at end of file diff --git a/vipra-ui/app/index.html b/vipra-ui/app/index.html index 7b7f84988ffe4e9068f3dc85caafe79d6dbe4015..fe6a3c37912a5303abd7a5866aee5118d0fb2787 100644 --- a/vipra-ui/app/index.html +++ b/vipra-ui/app/index.html @@ -59,6 +59,8 @@ </div><!-- /.container-fluid --> </nav> + <div ui-view="subNavBar"></div> + <div class="container" ui-view></div> <footer class="footer navbar-default"> diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js index 18dad55a42fcf73e50ae80215609e232f3b95a1e..a12294d9e9bed738a43e859eea7c71d35e533852 100644 --- a/vipra-ui/app/js/controllers.js +++ b/vipra-ui/app/js/controllers.js @@ -73,16 +73,18 @@ * Article Controllers */ - app.controller('ArticlesIndexController', ['$scope', '$stateParams', 'ArticleFactory', - function($scope, $stateParams, ArticleFactory, testService) { + app.controller('ArticlesIndexController', ['$scope', '$state', '$stateParams', 'ArticleFactory', 'Store', + function($scope, $state, $stateParams, ArticleFactory, Store) { $scope.page = Math.max($stateParams.page || 1, 1); $scope.limit = pageSize; + $scope.sort = Store('sortarticles-' + $state.current.name) || 'date'; - $scope.change = function(page) { + $scope.reload = function() { ArticleFactory.query({ - skip: (page-1)*$scope.limit, - limit: $scope.limit + skip: ($scope.page-1)*$scope.limit, + limit: $scope.limit, + sort: $scope.sort }, function(response) { $scope.articles = response.data; $scope.articlesMeta = response.meta; @@ -90,7 +92,12 @@ }); }; - $scope.change($scope.page); + $scope.changePage = function(page) { + $scope.page = page; + $scope.reload(); + }; + + $scope.reload(); }]); @@ -132,6 +139,7 @@ }; $scope.topicShare = topicShare; + }); }]); @@ -145,10 +153,11 @@ $scope.page = Math.max($stateParams.page || 1, 1); $scope.limit = pageSize; + $scope.sort = Store('sorttopics-' + $state.current.name) || 'name'; - $scope.change = function(page) { + $scope.reload = function() { TopicFactory.query({ - skip: (page-1)*$scope.limit, + skip: ($scope.page-1)*$scope.limit, limit: $scope.limit }, function(response) { $scope.topics = response.data; @@ -157,7 +166,12 @@ }); }; - $scope.change($scope.page); + $scope.changePage = function(page) { + $scope.page = page; + $scope.reload(); + }; + + $scope.reload(); }]); @@ -178,16 +192,18 @@ * Word Controllers */ - app.controller('WordsIndexController', ['$scope', '$stateParams', 'WordFactory', - function($scope, $stateParams, WordFactory) { + app.controller('WordsIndexController', ['$scope', '$state', '$stateParams', 'WordFactory', + function($scope, $state, $stateParams, WordFactory) { $scope.page = Math.max($stateParams.page || 1, 1); $scope.limit = 300; + $scope.sort = Store('sortwords-' + $state.current.name) || 'word'; - $scope.change = function(page) { + $scope.reload = function() { WordFactory.query({ - skip: (page-1)*$scope.limit, - limit: $scope.limit + skip: ($scope.page-1)*$scope.limit, + limit: $scope.limit, + sort: $scope.sort }, function(response) { $scope.words = response.data; $scope.wordsMeta = response.meta; @@ -195,7 +211,12 @@ }); }; - $scope.change($scope.page); + $scope.changePage = function(page) { + $scope.page = page; + $scope.reload(); + }; + + $scope.reload(); }]); @@ -215,7 +236,7 @@ * Directive Controllers */ - app.controller('PaginationController', ['$scope', + app.controller('PaginationController', ['$scope', function($scope) { if(!$scope.page) @@ -246,6 +267,7 @@ $scope.changePage = function(page) { $scope.page = page; change(page); + window.scrollTo(0,0); }; }]); diff --git a/vipra-ui/app/js/directives.js b/vipra-ui/app/js/directives.js index 4b0c64c862b840683e9ba829ff3f78f6719ea5c1..4335de7bdf1eac433ef4d37ae9a37f1d5316c8f7 100644 --- a/vipra-ui/app/js/directives.js +++ b/vipra-ui/app/js/directives.js @@ -57,7 +57,7 @@ }; }); - app.directive('visGraph', ['$state', '$timeout', 'ArticleFactory', 'TopicFactory', + app.directive('visNetwork', ['$state', '$timeout', 'ArticleFactory', 'TopicFactory', function($state, $timeout, ArticleFactory, TopicFactory) { return { diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less index 0919aec6625c909b61eb6df05b7055b5de5c6ba6..348c59eef3a204ec6399e7fef0c19f1b933b5254 100644 --- a/vipra-ui/app/less/app.less +++ b/vipra-ui/app/less/app.less @@ -13,6 +13,16 @@ a:hover { cursor:pointer; } +ul.dashed { + list-style: none; + padding: 0; + + li:before { + content: "–"; + padding-right: 5px; + } +} + .heading { .noselect; background: transparent url(/img/logo.svg) no-repeat 50% 50%; @@ -25,7 +35,9 @@ a:hover { padding: 15px; .search-result { - margin-bottom: 20px; + &:not(:last-child) { + margin-bottom: 20px; + } a { font-size: 1.5rem; diff --git a/vipra-util/src/main/java/de/vipra/util/AbstractCache.java b/vipra-util/src/main/java/de/vipra/util/AbstractCache.java new file mode 100644 index 0000000000000000000000000000000000000000..f80100acf8276c3fe27ac71e27ba1ec995c4aeb1 --- /dev/null +++ b/vipra-util/src/main/java/de/vipra/util/AbstractCache.java @@ -0,0 +1,15 @@ +package de.vipra.util; + +public interface AbstractCache<T, U> { + + U get(T t); + + void put(T t, U u); + + void remove(T t); + + boolean contains(T t); + + void clear(); + +} 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 692dc93efe156cde80e707079e73bf05a4614aab..fdac1542edca05c723d234099cebec77ecce2dc8 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 @@ -10,6 +10,7 @@ import java.util.Set; import org.mongodb.morphia.Datastore; import org.mongodb.morphia.query.Query; +import de.vipra.util.AbstractCache; import de.vipra.util.Config; import de.vipra.util.ListUtils; import de.vipra.util.Mongo; @@ -25,6 +26,7 @@ public class DatabaseService<Type extends Model<IdType>, IdType> implements Serv private final Class<Type> clazz; private final String[] ignoredFieldsSingle; private final String[] ignoredFieldsMulti; + private AbstractCache<IdType, Type> cache; public DatabaseService(Mongo mongo, Class<Type> clazz) { this.datastore = mongo.getDatastore(); @@ -48,12 +50,20 @@ public class DatabaseService<Type extends Model<IdType>, IdType> implements Serv @Override public Type getSingle(IdType id, String... fields) { - Query<Type> q = datastore.createQuery(clazz).field("_id").equal(id); - if (fields != null && fields.length > 0) - q.retrievedFields(true, setMinus(fields, ignoredFieldsSingle)); - else if (ignoredFieldsSingle.length > 0) - q.retrievedFields(false, ignoredFieldsSingle); - return q.get(); + Type t = null; + if (cache == null || (fields != null && fields.length > 0) || !cache.contains(id)) { + Query<Type> q = datastore.createQuery(clazz).field("_id").equal(id); + if (fields != null && fields.length > 0) + q.retrievedFields(true, setMinus(fields, ignoredFieldsSingle)); + else if (ignoredFieldsSingle.length > 0) + q.retrievedFields(false, ignoredFieldsSingle); + t = q.get(); + if (cache != null && (fields == null || fields.length == 0)) + cache.put(id, t); + } else { + t = cache.get(id); + } + return t; } @Override @@ -113,6 +123,8 @@ public class DatabaseService<Type extends Model<IdType>, IdType> implements Serv @Override public Type createSingle(Type t) throws DatabaseException { datastore.save(t); + if (cache != null) + cache.put(t.getId(), t); return t; } @@ -125,17 +137,24 @@ public class DatabaseService<Type extends Model<IdType>, IdType> implements Serv @Override public long deleteSingle(IdType id) throws DatabaseException { - return datastore.delete(id).getN(); + int deleted = datastore.delete(id).getN(); + if (cache != null) + cache.remove(id); + return deleted; } @Override public void updateSingle(Type t) throws DatabaseException { datastore.save(t); + if (cache != null) + cache.put(t.getId(), t); } @Override public void drop() { datastore.getCollection(clazz).drop(); + if (cache != null) + cache.clear(); } @Override @@ -143,6 +162,11 @@ public class DatabaseService<Type extends Model<IdType>, IdType> implements Serv return datastore.getCount(clazz); } + @Override + public void withCache(AbstractCache<IdType, Type> cache) { + this.cache = cache; + } + public static <Type extends Model<IdType>, IdType> DatabaseService<Type, IdType> getDatabaseService(Config config, Class<Type> clazz) throws ConfigException { Mongo mongo = config.getMongo(); 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 1062f540967f23e14bb14c17d596faf4e705b85c..16b40b15cc119cac7ffb9a2197c1638665492bfa 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 @@ -3,6 +3,7 @@ package de.vipra.util.service; import java.util.ArrayList; import java.util.List; +import de.vipra.util.AbstractCache; import de.vipra.util.Pair; import de.vipra.util.model.Model; @@ -34,6 +35,8 @@ public interface Service<Type extends Model<IdType>, IdType, E extends Exception long count() throws E; + void withCache(AbstractCache<IdType, Type> cache); + public static class QueryBuilder { private Integer skip; @@ -51,22 +54,27 @@ public interface Service<Type extends Model<IdType>, IdType, E extends Exception } public QueryBuilder skip(Integer skip) { - this.skip = skip; + if (skip == null || skip >= 0) + this.skip = skip; return this; } public QueryBuilder limit(Integer limit) { - this.limit = limit; + if (limit == null || limit >= 0) + this.limit = limit; return this; } public QueryBuilder sortBy(String sortBy) { - this.sortBy = sortBy; + if (sortBy == null || !sortBy.isEmpty()) + this.sortBy = sortBy; return this; } public QueryBuilder criteria(String field, Object value) { - return criteria(Pair.pair(field, value)); + if (field != null && value != null && !field.isEmpty()) + criteria(Pair.pair(field, value)); + return this; } public QueryBuilder criteria(Pair<String, Object> pair) {