From b4337fa853746256d5804df62370f9425f95d67d Mon Sep 17 00:00:00 2001 From: Eike Cochu <eike@cochu.com> Date: Mon, 25 Jan 2016 00:38:23 +0100 Subject: [PATCH] updated ui added imports resource, storing import operation information added import information to start page removed war file from repo, updated gitignore --- .gitignore | 1 + ma-impl.sublime-workspace | 98 ++-------- .../de/vipra/cmd/model/ProcessedArticle.java | 2 +- .../de/vipra/cmd/option/ClearCommand.java | 4 + .../de/vipra/cmd/option/ImportCommand.java | 60 ++++-- .../rest/provider/ObjectMapperProvider.java | 10 +- .../vipra/rest/resource/ArticleResource.java | 36 ++-- .../vipra/rest/resource/ImportResource.java | 139 ++++++++++++++ .../rest/serializer/GenericDeserializer.java | 2 + .../rest/serializer/GenericSerializer.java | 2 + vipra-ui/app/adapters/application.js | 2 +- vipra-ui/app/helpers/is-empty.js | 11 ++ vipra-ui/app/models/import.js | 12 ++ vipra-ui/app/router.js | 3 + vipra-ui/app/routes/index.js | 23 +++ vipra-ui/app/styles/app.scss | 21 +++ vipra-ui/app/templates/application.hbs | 2 +- vipra-ui/app/templates/index.hbs | 32 +++- vipra-ui/tests/unit/helpers/is-empty-test.js | 10 + vipra-ui/tests/unit/models/import-test.js | 12 ++ .../main/java/de/vipra/util/StringUtils.java | 29 +++ .../src/main/java/de/vipra/util/Timer.java | 8 +- .../src/main/java/de/vipra/util/WordMap.java | 9 +- .../java/de/vipra/util/model/Article.java | 140 +------------- .../java/de/vipra/util/model/ArticleFull.java | 175 ++++++++++++++++++ .../main/java/de/vipra/util/model/Import.java | 132 +++++++++++++ .../main/java/de/vipra/util/model/Topic.java | 6 + .../vipra/util/service/DatabaseService.java | 7 +- .../java/de/vipra/util/service/Service.java | 3 + 29 files changed, 717 insertions(+), 274 deletions(-) create mode 100644 vipra-rest/src/main/java/de/vipra/rest/resource/ImportResource.java create mode 100644 vipra-ui/app/helpers/is-empty.js create mode 100644 vipra-ui/app/models/import.js create mode 100644 vipra-ui/app/routes/index.js create mode 100644 vipra-ui/tests/unit/helpers/is-empty-test.js create mode 100644 vipra-ui/tests/unit/models/import-test.js create mode 100644 vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java create mode 100644 vipra-util/src/main/java/de/vipra/util/model/Import.java diff --git a/.gitignore b/.gitignore index 9878f61c..f19bca38 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.log *.jar +*.war .vagrant/ vm/webapps/ diff --git a/ma-impl.sublime-workspace b/ma-impl.sublime-workspace index 61e4ba59..ba2c4d36 100644 --- a/ma-impl.sublime-workspace +++ b/ma-impl.sublime-workspace @@ -279,22 +279,6 @@ }, "buffers": [ - { - "file": "vipra-ui/app/templates/articles/index.hbs", - "settings": - { - "buffer_size": 385, - "line_ending": "Unix" - } - }, - { - "file": "vipra-ui/app/routes/articles/index.js", - "settings": - { - "buffer_size": 1232, - "line_ending": "Unix" - } - } ], "build_system": "", "build_system_choices": @@ -476,20 +460,24 @@ "/home/eike/repos/master/ma-impl", "/home/eike/repos/master/ma-impl/vipra-ui", "/home/eike/repos/master/ma-impl/vipra-ui/app", + "/home/eike/repos/master/ma-impl/vipra-ui/app/models", "/home/eike/repos/master/ma-impl/vipra-ui/app/routes", "/home/eike/repos/master/ma-impl/vipra-ui/app/routes/articles", "/home/eike/repos/master/ma-impl/vipra-ui/app/styles", - "/home/eike/repos/master/ma-impl/vipra-ui/app/templates", - "/home/eike/repos/master/ma-impl/vipra-ui/app/templates/articles", - "/home/eike/repos/master/ma-impl/vipra-ui/app/templates/components" + "/home/eike/repos/master/ma-impl/vipra-ui/app/templates" ], "file_history": [ - "/home/eike/repos/master/ma-impl/vipra-ui/app/templates/articles/index.hbs", - "/home/eike/repos/master/ma-impl/vipra-ui/app/routes/articles/index.js", + "/home/eike/repos/master/ma-impl/vipra-ui/app/styles/app.scss", "/home/eike/repos/master/ma-impl/vipra-ui/app/templates/index.hbs", + "/home/eike/repos/master/ma-impl/vipra-ui/app/routes/index.js", + "/home/eike/repos/master/ma-impl/vipra-ui/app/models/import.js", "/home/eike/repos/master/ma-impl/vipra-ui/app/templates/application.hbs", - "/home/eike/repos/master/ma-impl/vipra-ui/app/styles/app.scss", + "/home/eike/repos/master/ma-impl/vipra-ui/app/helpers/is-empty.js", + "/home/eike/repos/master/ma-impl/vipra-ui/app/router.js", + "/home/eike/repos/master/ma-impl/vipra-ui/app/templates/articles/index.hbs", + "/home/eike/repos/master/ma-impl/vipra-ui/app/routes/articles/index.js", + "/home/eike/repos/master/ma-impl/vipra-ui/app/adapters/application.js", "/home/eike/repos/master/ma-impl/vipra-ui/app/styles/sticky-footer.scss", "/home/eike/repos/master/ma-impl/vipra-ui/app/controllers/application.js", "/home/eike/repos/master/ma-impl/vipra-ui/app/components/scroll-top.js", @@ -509,7 +497,6 @@ "/home/eike/.config/sublime-text-3/Packages/User/Preferences.sublime-settings", "/home/eike/repos/master/ma-impl/vipra-ui/app/templates/topics.hbs", "/home/eike/repos/master/ma-impl/vipra-ui/app/templates/words.hbs", - "/home/eike/repos/master/ma-impl/vipra-ui/app/adapters/application.js", "/home/eike/repos/master/ma-impl/vipra-ui/app/templates/articles.hbs", "/home/eike/repos/master/ma-impl/vipra-ui/ember-cli-build.js", "/home/eike/repos/master/ma-impl/vipra-ui/app/templates/loading.hbs", @@ -521,7 +508,6 @@ "/home/eike/repos/master/ma-impl/vipra-ui/app/models/word.js", "/home/eike/repos/master/ma-impl/vipra-ui/app/routes/words/index.js", "/home/eike/repos/master/ma-impl/vipra-ui/app/components/items-list.js", - "/home/eike/repos/master/ma-impl/vipra-ui/app/router.js", "/home/eike/repos/master/ma-impl/vipra-ui/app/routes/words", "/home/eike/repos/master/ma-impl/vipra-ui/app/models/topic.js", "/home/eike/repos/master/ma-impl/vipra-ui/app/models/article.js", @@ -609,10 +595,7 @@ "/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/components/search-box.js", "/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/templates/loading.hbs", "/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/templates/not-found.hbs", - "/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/templates/articles.hbs", - "/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/models/article.js", - "/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/templates/index.hbs", - "/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/routes/articles/index.js" + "/home/eike/Repositories/fu/ss15/ma/impl/vipra-ui/app/templates/articles.hbs" ], "find": { @@ -936,67 +919,8 @@ "groups": [ { - "selected": 1, "sheets": [ - { - "buffer": 0, - "file": "vipra-ui/app/templates/articles/index.hbs", - "semi_transient": false, - "settings": - { - "buffer_size": 385, - "regions": - { - }, - "selection": - [ - [ - 385, - 385 - ] - ], - "settings": - { - "syntax": "Packages/Handlebars/grammars/Handlebars.tmLanguage" - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 1, - "type": "text" - }, - { - "buffer": 1, - "file": "vipra-ui/app/routes/articles/index.js", - "semi_transient": false, - "settings": - { - "buffer_size": 1232, - "regions": - { - }, - "selection": - [ - [ - 1212, - 1212 - ] - ], - "settings": - { - "syntax": "Packages/JavaScriptNext - ES6 Syntax/JavaScriptNext.tmLanguage", - "tab_size": 2, - "translate_tabs_to_spaces": true - }, - "translation.x": -0.0, - "translation.y": 663.0, - "zoom_level": 1.0 - }, - "stack_index": 0, - "type": "text" - } ] } ], diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/model/ProcessedArticle.java b/vipra-cmd/src/main/java/de/vipra/cmd/model/ProcessedArticle.java index aea42f74..484edc6b 100644 --- a/vipra-cmd/src/main/java/de/vipra/cmd/model/ProcessedArticle.java +++ b/vipra-cmd/src/main/java/de/vipra/cmd/model/ProcessedArticle.java @@ -8,7 +8,7 @@ import de.vipra.cmd.text.ProcessedText; @SuppressWarnings("serial") @Entity(value = "articles", noClassnameStored = true) -public class ProcessedArticle extends de.vipra.util.model.Article { +public class ProcessedArticle extends de.vipra.util.model.ArticleFull { @Transient private ProcessedText processedText; diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/ClearCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/ClearCommand.java index b56dd596..a71a50a6 100644 --- a/vipra-cmd/src/main/java/de/vipra/cmd/option/ClearCommand.java +++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/ClearCommand.java @@ -14,6 +14,7 @@ import de.vipra.cmd.model.ProcessedArticle; import de.vipra.util.Config; import de.vipra.util.ConsoleUtils; import de.vipra.util.ex.ConfigException; +import de.vipra.util.model.Import; import de.vipra.util.model.TopicFull; import de.vipra.util.model.Word; import de.vipra.util.service.DatabaseService; @@ -28,6 +29,7 @@ public class ClearCommand implements Command { private DatabaseService<ProcessedArticle, ObjectId> dbArticles; private DatabaseService<TopicFull, ObjectId> dbTopics; private DatabaseService<Word, String> dbWords; + private DatabaseService<Import, ObjectId> dbImports; public ClearCommand(boolean defaults) { this.defaults = defaults; @@ -39,6 +41,7 @@ public class ClearCommand implements Command { dbArticles = DatabaseService.getDatabaseService(config, ProcessedArticle.class); dbTopics = DatabaseService.getDatabaseService(config, TopicFull.class); dbWords = DatabaseService.getDatabaseService(config, Word.class); + dbImports = DatabaseService.getDatabaseService(config, Import.class); } catch (Exception e) { throw new ClearException(e); } @@ -47,6 +50,7 @@ public class ClearCommand implements Command { dbArticles.drop(); dbTopics.drop(); dbWords.drop(); + dbImports.drop(); out.info("clearing filebase"); File dataDir = config.getDataDirectory(); 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 7cefa773..7cafd4a3 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 @@ -34,7 +34,10 @@ import de.vipra.util.StringUtils; import de.vipra.util.Timer; import de.vipra.util.WordMap; import de.vipra.util.ex.DatabaseException; +import de.vipra.util.model.Article; import de.vipra.util.model.ArticleStats; +import de.vipra.util.model.Import; +import de.vipra.util.model.Topic; import de.vipra.util.model.TopicFull; import de.vipra.util.model.TopicRef; import de.vipra.util.model.Word; @@ -51,6 +54,7 @@ public class ImportCommand implements Command { private DatabaseService<ProcessedArticle, ObjectId> dbArticles; private DatabaseService<TopicFull, ObjectId> dbTopics; private DatabaseService<Word, String> dbWords; + private DatabaseService<Import, ObjectId> dbImports; private Filebase filebase; private Processor preprocessor; private WordMap wordMap; @@ -103,8 +107,8 @@ public class ImportCommand implements Command { * @return * @throws ImportException */ - private void importArticle(JSONObject obj) throws ImportException { - out.debug("importing \"" + StringUtils.ellipsize(obj.get("title").toString(), 80) + "\""); + private Article importArticle(JSONObject obj) throws ImportException { + out.info("importing \"" + StringUtils.ellipsize(obj.get("title").toString(), 80) + "\""); ProcessedArticle article = new ProcessedArticle(); article.fromJSON(obj); @@ -120,6 +124,9 @@ public class ImportCommand implements Command { // add article to filebase filebase.add(article); + + // return article reference + return new Article(article.getId()); } catch (Exception e) { throw new ImportException(e, article.getId().toString()); } @@ -135,31 +142,29 @@ public class ImportCommand implements Command { * @throws ImportException * @throws Exception */ - private long importFile(File file) throws FileNotFoundException, IOException, ParseException, ImportException { + private List<Article> importFile(File file) + throws FileNotFoundException, IOException, ParseException, ImportException { Object data = parser.parse(new FileReader(file)); - - long imported = 0; + List<Article> articles = new ArrayList<>(); if (data instanceof JSONArray) { for (Object object : (JSONArray) data) { - importArticle((JSONObject) object); - imported++; + articles.add(importArticle((JSONObject) object)); } } else if (data instanceof JSONObject) { - importArticle((JSONObject) data); - imported++; + articles.add(importArticle((JSONObject) data)); } - return imported; + return articles; } - private long importFiles(List<File> files) + private List<Article> importFiles(List<File> files) throws FileNotFoundException, IOException, ParseException, ImportException { - long imported = 0; + List<Article> articles = new ArrayList<>(); for (File file : files) { - imported += importFile(file); + articles.addAll(importFile(file)); } - return imported; + return articles; } @Override @@ -169,6 +174,7 @@ public class ImportCommand implements Command { dbArticles = DatabaseService.getDatabaseService(config, ProcessedArticle.class); dbTopics = DatabaseService.getDatabaseService(config, TopicFull.class); dbWords = DatabaseService.getDatabaseService(config, Word.class); + dbImports = DatabaseService.getDatabaseService(config, Import.class); filebase = Filebase.getFilebase(config); preprocessor = Processor.getPreprocessor(config); wordMap = new WordMap(dbWords); @@ -181,11 +187,14 @@ public class ImportCommand implements Command { Timer timer = new Timer(); timer.start(); + Import importOp = new Import(); + /* * import files into database and filebase */ out.info("file import"); - long imported = importFiles(files); + List<Article> importedArticles = importFiles(files); + importOp.setArticles(importedArticles); timer.lap("import"); /* @@ -211,15 +220,19 @@ public class ImportCommand implements Command { Map<String, String> topicIndexMap = new HashMap<>(); dbTopics.drop(); List<TopicFull> newTopicDefs = new ArrayList<>(batchSize); + List<Topic> newTopicRefs = new ArrayList<>(); Iterator<TopicFull> it = topicDefs.iterator(); while (it.hasNext()) { newTopicDefs.add(it.next()); if (newTopicDefs.size() == batchSize || !it.hasNext()) { dbTopics.createMultiple(newTopicDefs); - for (TopicFull newTopicDef : newTopicDefs) + for (TopicFull newTopicDef : newTopicDefs) { topicIndexMap.put(Integer.toString(newTopicDef.getIndex()), newTopicDef.getId().toString()); + newTopicRefs.add(new Topic(newTopicDef.getId())); + } } } + importOp.setTopics(newTopicRefs); timer.lap("saving topics"); /* @@ -250,14 +263,23 @@ public class ImportCommand implements Command { log.error("could not update article: " + a.getTitle() + " (" + a.getId() + ")"); } } + List<Word> importedWords = wordMap.getNewWords(); + importOp.setWords(importedWords); timer.lap("saving topic refs"); + /* + * save import information + */ + importOp.setDuration(timer.total()); + dbImports.createSingle(importOp); + /* * run information */ - out.info("imported " + imported + " new " + StringUtils.quantity(imported, "article")); - long newWords = wordMap.getNewWords(); - out.info("imported " + newWords + " new " + StringUtils.quantity(newWords, "word")); + int newArticlesCount = importedArticles.size(); + int newWordsCount = importedWords.size(); + out.info("imported " + newArticlesCount + " new " + StringUtils.quantity(newArticlesCount, "article")); + out.info("imported " + newWordsCount + " new " + StringUtils.quantity(newWordsCount, "word")); out.info(timer.toString()); } catch (Exception e) { throw new ExecutionException(e); 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 2b1060cf..fcab9e67 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 @@ -19,7 +19,8 @@ import de.vipra.rest.serializer.GenericSerializer; import de.vipra.rest.serializer.ObjectIdDeserializer; import de.vipra.rest.serializer.ObjectIdSerializer; import de.vipra.util.Constants; -import de.vipra.util.model.Article; +import de.vipra.util.model.ArticleFull; +import de.vipra.util.model.Import; import de.vipra.util.model.TopicFull; import de.vipra.util.model.Word; @@ -41,8 +42,8 @@ public class ObjectMapperProvider implements ContextResolver<ObjectMapper> { public static ObjectMapper createDefaultMapper() { SimpleModule module = new SimpleModule(); - module.addSerializer(Article.class, new GenericSerializer<Article>(Article.class)); - module.addDeserializer(Article.class, new GenericDeserializer<Article>(Article.class)); + module.addSerializer(ArticleFull.class, new GenericSerializer<ArticleFull>(ArticleFull.class)); + module.addDeserializer(ArticleFull.class, new GenericDeserializer<ArticleFull>(ArticleFull.class)); module.addSerializer(TopicFull.class, new GenericSerializer<TopicFull>(TopicFull.class)); module.addDeserializer(TopicFull.class, new GenericDeserializer<TopicFull>(TopicFull.class)); @@ -50,6 +51,9 @@ public class ObjectMapperProvider implements ContextResolver<ObjectMapper> { module.addSerializer(Word.class, new GenericSerializer<Word>(Word.class)); module.addDeserializer(Word.class, new GenericDeserializer<Word>(Word.class)); + module.addSerializer(Import.class, new GenericSerializer<Import>(Import.class)); + module.addDeserializer(Import.class, new GenericDeserializer<Import>(Import.class)); + module.addSerializer(ObjectId.class, new ObjectIdSerializer()); module.addDeserializer(ObjectId.class, new ObjectIdDeserializer()); 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 204b2f1b..3c9e531a 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 @@ -36,7 +36,7 @@ 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.Article; +import de.vipra.util.model.ArticleFull; import de.vipra.util.service.DatabaseService; @Path("articles") @@ -45,18 +45,18 @@ public class ArticleResource { @Context UriInfo uri; - final Cache<String, Article> cache; - final DatabaseService<Article, ObjectId> service; + final Cache<String, ArticleFull> cache; + final DatabaseService<ArticleFull, ObjectId> service; public ArticleResource(@Context ServletContext servletContext) throws ConfigException, IOException { Config config = Config.getConfig(); - service = DatabaseService.getDatabaseService(config, Article.class); + service = DatabaseService.getDatabaseService(config, ArticleFull.class); CacheManager manager = (CacheManager) servletContext.getAttribute("cachemanager"); - Cache<String, Article> articleCache = manager.getCache("articlecache", String.class, Article.class); + Cache<String, ArticleFull> articleCache = manager.getCache("articlecache", String.class, ArticleFull.class); if (articleCache == null) articleCache = manager.createCache("articlecache", - CacheConfigurationBuilder.newCacheConfigurationBuilder().buildConfig(String.class, Article.class)); + CacheConfigurationBuilder.newCacheConfigurationBuilder().buildConfig(String.class, ArticleFull.class)); this.cache = articleCache; } @@ -64,7 +64,7 @@ public class ArticleResource { @Produces(APIMediaType.APPLICATION_JSONAPI) public Response getArticles(@QueryParam("skip") Integer skip, @QueryParam("limit") Integer limit, @QueryParam("sort") @DefaultValue("date") String sortBy, @QueryParam("fields") String fields) { - Wrapper<List<Article>> res = new Wrapper<>(); + Wrapper<List<ArticleFull>> res = new Wrapper<>(); if (skip != null && limit != null) res.addPaginationLinks(uri.getAbsolutePath(), skip, limit, service.count()); @@ -73,7 +73,7 @@ public class ArticleResource { return res.badRequest(); try { - List<Article> articles = service.getMultiple(skip, limit, sortBy, StringUtils.getFields(fields)); + List<ArticleFull> articles = service.getMultiple(skip, limit, sortBy, StringUtils.getFields(fields)); if((skip != null && skip > 0) || (limit != null && limit > 0)) res.addMeta("total", service.count()); @@ -92,14 +92,14 @@ public class ArticleResource { @Consumes(APIMediaType.APPLICATION_JSONAPI) @Path("{id}") public Response getArticle(@PathParam("id") String id, @QueryParam("fields") String fields) { - Wrapper<Article> res = new Wrapper<>(); + Wrapper<ArticleFull> res = new Wrapper<>(); if (id == null || id.trim().length() == 0) { res.addError(new APIError(Response.Status.BAD_REQUEST, "ID is empty", String.format(Messages.BAD_REQUEST, "id cannot be empty"))); return res.badRequest(); } - Article article; + ArticleFull article; try { article = getSingle(id, StringUtils.getFields(fields)); } catch (Exception e) { @@ -119,8 +119,8 @@ public class ArticleResource { @POST @Consumes(APIMediaType.APPLICATION_JSONAPI) @Produces(APIMediaType.APPLICATION_JSONAPI) - public Response createArticle(Article article) { - Wrapper<Article> res; + public Response createArticle(ArticleFull article) { + Wrapper<ArticleFull> res; try { article = service.createSingle(article); res = new Wrapper<>(article); @@ -136,7 +136,7 @@ public class ArticleResource { @DELETE @Path("{id}") public Response deleteArticle(@PathParam("id") String id) { - Wrapper<Article> res = new Wrapper<>(); + Wrapper<ArticleFull> res = new Wrapper<>(); long deleted; try { deleted = service.deleteSingle(MongoUtils.objectId(id)); @@ -163,9 +163,9 @@ public class ArticleResource { @Consumes(APIMediaType.APPLICATION_JSONAPI) @Produces(APIMediaType.APPLICATION_JSONAPI) @Path("{id}") - public Response replaceArticle(@PathParam("id") String id, Wrapper<Article> wrapper) { - Article article = wrapper.getData(); - Wrapper<Article> res = new Wrapper<>(); + public Response replaceArticle(@PathParam("id") String id, Wrapper<ArticleFull> wrapper) { + ArticleFull article = wrapper.getData(); + Wrapper<ArticleFull> res = new Wrapper<>(); try { service.updateSingle(article); cache.put(id, article); @@ -177,9 +177,9 @@ public class ArticleResource { } } - private Article getSingle(String id, String[] fields) { + private ArticleFull getSingle(String id, String[] fields) { if (fields == null || fields.length == 0) { - Article article = cache.get(id); + ArticleFull article = cache.get(id); if (article == null) { article = service.getSingle(MongoUtils.objectId(id)); if (article != null) 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 new file mode 100644 index 00000000..f4ba2086 --- /dev/null +++ b/vipra-rest/src/main/java/de/vipra/rest/resource/ImportResource.java @@ -0,0 +1,139 @@ +package de.vipra.rest.resource; + +import java.io.IOException; +import java.util.List; + +import javax.servlet.ServletContext; +import javax.ws.rs.Consumes; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +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.APIMediaType; +import de.vipra.rest.Messages; +import de.vipra.rest.model.APIError; +import de.vipra.rest.model.Wrapper; +import de.vipra.util.Config; +import de.vipra.util.MongoUtils; +import de.vipra.util.StringUtils; +import de.vipra.util.ex.ConfigException; +import de.vipra.util.model.Import; +import de.vipra.util.service.DatabaseService; + +@Path("imports") +public class ImportResource { + + @Context + UriInfo uri; + + final Cache<String, Import> cache; + final DatabaseService<Import, ObjectId> service; + + public ImportResource(@Context ServletContext servletContext) throws ConfigException, IOException { + Config config = Config.getConfig(); + service = DatabaseService.getDatabaseService(config, Import.class); + + CacheManager manager = (CacheManager) servletContext.getAttribute("cachemanager"); + Cache<String, Import> importCache = manager.getCache("importcache", String.class, Import.class); + if (importCache == null) + importCache = manager.createCache("importcache", + CacheConfigurationBuilder.newCacheConfigurationBuilder().buildConfig(String.class, Import.class)); + this.cache = importCache; + } + + @GET + @Produces(APIMediaType.APPLICATION_JSONAPI) + public Response getImports(@QueryParam("skip") Integer skip, @QueryParam("limit") Integer limit, + @QueryParam("sort") @DefaultValue("date") String sortBy, @QueryParam("fields") String fields) { + Wrapper<List<Import>> res = new Wrapper<>(); + + if (skip != null && limit != null) + res.addPaginationLinks(uri.getAbsolutePath(), skip, limit, service.count()); + + if (res.hasErrors()) + return Response.status(Response.Status.BAD_REQUEST).entity(res).build(); + + try { + List<Import> imports = service.getMultiple(skip, limit, sortBy, false, StringUtils.getFields(fields)); + + if ((skip != null && skip > 0) || (limit != null && limit > 0)) + res.addMeta("total", service.count()); + else + res.addMeta("total", imports.size()); + + return res.ok(imports); + } catch (Exception e) { + res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage())); + return Response.status(Response.Status.BAD_REQUEST).entity(res).build(); + } + } + + @GET + @Produces(APIMediaType.APPLICATION_JSONAPI) + @Consumes(APIMediaType.APPLICATION_JSONAPI) + @Path("latest") + public Response getLatestImport(@QueryParam("fields") String fields) { + Wrapper<Import> res = new Wrapper<>(); + List<Import> latestImport = service.getMultiple(0, 1, "date", StringUtils.getFields(fields)); + + if (latestImport == null || latestImport.size() != 1) { + return res.noContent(); + } else { + return res.ok(latestImport.get(0)); + } + } + + @GET + @Produces(APIMediaType.APPLICATION_JSONAPI) + @Consumes(APIMediaType.APPLICATION_JSONAPI) + @Path("{id}") + public Response getImport(@PathParam("id") String id, @QueryParam("fields") String fields) { + Wrapper<Import> res = new Wrapper<>(); + if (id == null || id.trim().length() == 0) { + res.addError(new APIError(Response.Status.BAD_REQUEST, "ID is empty", + String.format(Messages.BAD_REQUEST, "id cannot be empty"))); + return res.badRequest(); + } + + Import importOp; + try { + importOp = getSingle(id, StringUtils.getFields(fields)); + } catch (Exception e) { + res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage())); + return res.badRequest(); + } + + if (importOp != null) + return res.ok(importOp); + else { + res.addError(new APIError(Response.Status.NOT_FOUND, "Resource not found", + String.format(Messages.NOT_FOUND, "import", id))); + return res.notFound(); + } + } + + private Import getSingle(String id, String[] fields) { + if (fields == null || fields.length == 0) { + Import importOp = cache.get(id); + if (importOp == null) { + importOp = service.getSingle(MongoUtils.objectId(id)); + if (importOp != null) + cache.put(id, importOp); + } + return importOp; + } else + return service.getSingle(MongoUtils.objectId(id), fields); + } + +} diff --git a/vipra-rest/src/main/java/de/vipra/rest/serializer/GenericDeserializer.java b/vipra-rest/src/main/java/de/vipra/rest/serializer/GenericDeserializer.java index 9566837f..d7842dd3 100644 --- a/vipra-rest/src/main/java/de/vipra/rest/serializer/GenericDeserializer.java +++ b/vipra-rest/src/main/java/de/vipra/rest/serializer/GenericDeserializer.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; +import de.vipra.util.StringUtils; import de.vipra.util.an.JsonWrap; import de.vipra.util.model.Model; @@ -49,6 +50,7 @@ public class GenericDeserializer<T extends Model<?>> extends JsonDeserializer<T> if (jw != null) name = jw.value() + "." + name; + name = StringUtils.camelToDashCase(name); allFields.put(name, field); String[] parts = name.split("\\."); diff --git a/vipra-rest/src/main/java/de/vipra/rest/serializer/GenericSerializer.java b/vipra-rest/src/main/java/de/vipra/rest/serializer/GenericSerializer.java index d58ca461..801c3d89 100644 --- a/vipra-rest/src/main/java/de/vipra/rest/serializer/GenericSerializer.java +++ b/vipra-rest/src/main/java/de/vipra/rest/serializer/GenericSerializer.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import de.vipra.util.NestedMap; +import de.vipra.util.StringUtils; import de.vipra.util.an.JsonType; import de.vipra.util.an.JsonWrap; import de.vipra.util.model.Model; @@ -57,6 +58,7 @@ public class GenericSerializer<T extends Model<?>> extends JsonSerializer<T> { if (jw != null) name = jw.value() + "." + name; + name = StringUtils.camelToDashCase(name); foundFields.put(name, field); } } diff --git a/vipra-ui/app/adapters/application.js b/vipra-ui/app/adapters/application.js index c14dc91b..0d442b61 100644 --- a/vipra-ui/app/adapters/application.js +++ b/vipra-ui/app/adapters/application.js @@ -1,7 +1,7 @@ import DS from 'ember-data'; export default DS.JSONAPIAdapter.extend({ - host: `http://${window.location.hostname}:8000`, + host: `http://${window.location.hostname}:8080`, namespace: 'vipra-rest', updateRecord(store, type, snapshot) { var data = {}; diff --git a/vipra-ui/app/helpers/is-empty.js b/vipra-ui/app/helpers/is-empty.js new file mode 100644 index 00000000..624b9ccb --- /dev/null +++ b/vipra-ui/app/helpers/is-empty.js @@ -0,0 +1,11 @@ +import Ember from 'ember'; + +export function isEmpty(params/*, hash*/) { + let ary = params[0], + text = params[1]; + if(!ary || typeof ary.length === "undefined" || !ary.length) + return text; + return null; +} + +export default Ember.Helper.helper(isEmpty); diff --git a/vipra-ui/app/models/import.js b/vipra-ui/app/models/import.js new file mode 100644 index 00000000..f7cbb7b5 --- /dev/null +++ b/vipra-ui/app/models/import.js @@ -0,0 +1,12 @@ +import DS from 'ember-data'; + +export default DS.Model.extend({ + date: DS.attr('date'), + duration: DS.attr(), + articles: DS.attr(), + articlesCount: DS.attr(), + topics: DS.attr(), + topicsCount: DS.attr(), + words: DS.attr(), + wordsCount: DS.attr() +}); diff --git a/vipra-ui/app/router.js b/vipra-ui/app/router.js index 1d31b063..c4a21fcf 100644 --- a/vipra-ui/app/router.js +++ b/vipra-ui/app/router.js @@ -9,14 +9,17 @@ Router.map(function() { this.route('articles', function() { this.route('show', { path: '/:article_id' }); }); + this.route('topics', function() { this.route('show', { path: '/:topic_id' }, function() { this.route('edit'); }); }); + this.route('words', function() { this.route('show', { path: '/:word_id' }); }); + this.route('not-found', { path: '/*:' }); }); diff --git a/vipra-ui/app/routes/index.js b/vipra-ui/app/routes/index.js new file mode 100644 index 00000000..e928d1e7 --- /dev/null +++ b/vipra-ui/app/routes/index.js @@ -0,0 +1,23 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ + model() { + return Ember.RSVP.hash({ + imports: this.store.findAll('import', { + skip: 0, + limit: 5 + }), + + latestimport: this.store.find('import', 'latest') + }); + }, + + afterModel(model) { + let articles = model.latestimport.get('articles'), + topics = model.latestimport.get('topics'), + words = model.latestimport.get('words'); + model.latestarticles = articles.slice(Math.max(articles.length - 5, 0)); + model.latesttopics = topics.slice(Math.max(topics.length - 5, 0)); + model.latestwords = words.slice(Math.max(words.length - 5, 0)); + } +}); \ No newline at end of file diff --git a/vipra-ui/app/styles/app.scss b/vipra-ui/app/styles/app.scss index 0c0ea97f..e072b7b5 100644 --- a/vipra-ui/app/styles/app.scss +++ b/vipra-ui/app/styles/app.scss @@ -1,3 +1,12 @@ +@mixin noselect { + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: default; +} + body { // for navbar padding-top: 60px; @@ -22,6 +31,18 @@ body { height: 120px; } +.heading { + @include noselect; + font-size: 82px; + margin: 30px 0; +} + +.ellipsize { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + .navbar-default { .navbar-nav { &> .active { diff --git a/vipra-ui/app/templates/application.hbs b/vipra-ui/app/templates/application.hbs index 16d58172..17ab63e8 100644 --- a/vipra-ui/app/templates/application.hbs +++ b/vipra-ui/app/templates/application.hbs @@ -8,7 +8,7 @@ <span class="icon-bar"></span> <span class="icon-bar"></span> </button> - {{link-to 'Vipra' 'index' class='navbar-brand'}} + {{link-to 'vipra' 'index' class='navbar-brand'}} </div> <!-- Collect the nav links, forms, and other content for toggling --> diff --git a/vipra-ui/app/templates/index.hbs b/vipra-ui/app/templates/index.hbs index 95736593..40099f48 100644 --- a/vipra-ui/app/templates/index.hbs +++ b/vipra-ui/app/templates/index.hbs @@ -2,15 +2,39 @@ <div class="row"> <div class="col-md-12"> <div class="text-center"> - <h1>Vipra</h1> + <h1 class="heading">'vɪprə</h1> </div> - <input type="text" class="form-control input-lg" placeholder="Search..."> + + <br> + {{debounced-input class='form-control input-lg' placeholder='Search...' value=filter debounce='500'}} </div> </div> + <br><br> <div class="row"> - <div class="col-md-12"> - + <div class="col-md-4 text-center"> + <h4>Latest articles</h4> + <ul class="list-unstyled"> + {{#each model.latestarticles as |article|}} + <li class="ellipsize">{{#link-to 'articles.show' article.id title=article.title}}{{article.title}}{{/link-to}}</li> + {{/each}} + </ul> + </div> + <div class="col-md-4 text-center"> + <h4>Latest topics</h4> + <ul class="list-unstyled"> + {{#each model.latesttopics as |topic|}} + <li class="ellipsize">{{#link-to 'topics.show' topic.id}}{{topic.name}}{{/link-to}}</li> + {{/each}} + </ul> + </div> + <div class="col-md-4 text-center"> + <h4>Latest words</h4> + <ul class="list-unstyled"> + {{#each model.latestwords as |word|}} + <li class="ellipsize">{{#link-to 'words.show' word.id}}{{word.id}}{{/link-to}}</li> + {{/each}} + </ul> </div> </div> diff --git a/vipra-ui/tests/unit/helpers/is-empty-test.js b/vipra-ui/tests/unit/helpers/is-empty-test.js new file mode 100644 index 00000000..cc3fb38e --- /dev/null +++ b/vipra-ui/tests/unit/helpers/is-empty-test.js @@ -0,0 +1,10 @@ +import { isEmpty } from '../../../helpers/is-empty'; +import { module, test } from 'qunit'; + +module('Unit | Helper | is empty'); + +// Replace this with your real tests. +test('it works', function(assert) { + let result = isEmpty(42); + assert.ok(result); +}); diff --git a/vipra-ui/tests/unit/models/import-test.js b/vipra-ui/tests/unit/models/import-test.js new file mode 100644 index 00000000..569cba60 --- /dev/null +++ b/vipra-ui/tests/unit/models/import-test.js @@ -0,0 +1,12 @@ +import { moduleForModel, test } from 'ember-qunit'; + +moduleForModel('import', 'Unit | Model | import', { + // Specify the other units that are required for this test. + needs: [] +}); + +test('it exists', function(assert) { + let model = this.subject(); + // let store = this.store(); + assert.ok(!!model); +}); diff --git a/vipra-util/src/main/java/de/vipra/util/StringUtils.java b/vipra-util/src/main/java/de/vipra/util/StringUtils.java index f45f5cc3..e093670a 100644 --- a/vipra-util/src/main/java/de/vipra/util/StringUtils.java +++ b/vipra-util/src/main/java/de/vipra/util/StringUtils.java @@ -143,4 +143,33 @@ public class StringUtils { return fields.split(","); } + public static String capitalize(String in) { + if (in == null || in.length() == 0) + return in; + return in.substring(0, 1).toUpperCase() + in.substring(1); + } + + public static String decapitalize(String in) { + if (in == null || in.length() == 0) + return in; + return in.substring(0, 1).toLowerCase() + in.substring(1); + } + + public static String camelToDashCase(String in) { + if (in == null || in.length() == 0) + return in; + return in.replaceAll("([A-Z])", "-$1").toLowerCase(); + } + + public static String dashToCamelCase(String in) { + if (in == null || in.length() == 0) + return in; + StringBuilder sb = new StringBuilder(); + String[] parts = in.split("-"); + sb.append(parts[0]); + for (int i = 1; i < parts.length; i++) + sb.append(StringUtils.capitalize(parts[i])); + return sb.toString(); + } + } diff --git a/vipra-util/src/main/java/de/vipra/util/Timer.java b/vipra-util/src/main/java/de/vipra/util/Timer.java index 1ec369f9..bfaf35b8 100644 --- a/vipra-util/src/main/java/de/vipra/util/Timer.java +++ b/vipra-util/src/main/java/de/vipra/util/Timer.java @@ -6,11 +6,12 @@ import java.util.Map.Entry; public class Timer { + private long firstStart; private long start; private Map<String, Long> laps; public long start() { - start = System.nanoTime(); + firstStart = start = System.nanoTime(); laps = new LinkedHashMap<>(); return start; } @@ -31,6 +32,11 @@ public class Timer { return lap; } + public long total() { + return System.nanoTime() - firstStart; + } + + @Override public String toString() { String out = null; if (laps != null && laps.size() > 0) { diff --git a/vipra-util/src/main/java/de/vipra/util/WordMap.java b/vipra-util/src/main/java/de/vipra/util/WordMap.java index 69be359d..792f034e 100644 --- a/vipra-util/src/main/java/de/vipra/util/WordMap.java +++ b/vipra-util/src/main/java/de/vipra/util/WordMap.java @@ -19,12 +19,13 @@ public class WordMap { private final DatabaseService<Word, String> dbWords; private final Map<String, Word> wordMap; + private final List<Word> newWords; private boolean createNow = true; - private long newWords = 0; public WordMap(DatabaseService<Word, String> dbWords) { this.dbWords = dbWords; this.wordMap = new HashMap<>(); + this.newWords = new ArrayList<>(); List<Word> words = dbWords.getAll(); for (Word word : words) wordMap.put(word.getWord().toLowerCase(), word); @@ -45,7 +46,7 @@ public class WordMap { if (createNow) { try { dbWords.createSingle(word); - newWords++; + newWords.add(word); } catch (DatabaseException e) { log.error("could not create word in database", e); throw new RuntimeException(e); @@ -60,7 +61,7 @@ public class WordMap { if (!e.getValue().isCreated()) newWords.add(e.getValue()); dbWords.createMultiple(newWords); - this.newWords += newWords.size(); + this.newWords.addAll(newWords); } public boolean isCreateNow() { @@ -71,7 +72,7 @@ public class WordMap { this.createNow = createNow; } - public long getNewWords() { + public List<Word> getNewWords() { return newWords; } diff --git a/vipra-util/src/main/java/de/vipra/util/model/Article.java b/vipra-util/src/main/java/de/vipra/util/model/Article.java index 131a99a2..e127b6e3 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/Article.java +++ b/vipra-util/src/main/java/de/vipra/util/model/Article.java @@ -1,66 +1,30 @@ package de.vipra.util.model; -import java.io.File; -import java.io.IOException; import java.io.Serializable; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; import org.bson.types.ObjectId; -import org.mongodb.morphia.annotations.Embedded; import org.mongodb.morphia.annotations.Entity; import org.mongodb.morphia.annotations.Id; import org.mongodb.morphia.annotations.Index; import org.mongodb.morphia.annotations.Indexes; -import org.mongodb.morphia.annotations.PrePersist; -import de.vipra.util.Constants; -import de.vipra.util.FileUtils; -import de.vipra.util.MongoUtils; -import de.vipra.util.StringUtils; import de.vipra.util.an.JsonType; -import de.vipra.util.an.JsonWrap; -import de.vipra.util.an.QueryIgnore; @SuppressWarnings("serial") @JsonType("article") @Entity(value = "articles", noClassnameStored = true) @Indexes({ @Index("title"), @Index("date") }) -public class Article extends FileModel<ObjectId> implements Serializable { +public class Article implements Model<ObjectId>, Serializable { @Id private ObjectId id; - - @JsonWrap("attributes") private String title; - @JsonWrap("attributes") - @QueryIgnore(multi = true) - private String text; - - @JsonWrap("attributes") - private String url; - - @JsonWrap("attributes") - private Date date; - - @Embedded - @JsonWrap("attributes") - @QueryIgnore(multi = true) - private List<TopicRef> topics; + public Article() {} - @Embedded - @JsonWrap("attributes") - @QueryIgnore(multi = true) - private ArticleStats stats; - - @JsonWrap("attributes") - private Date created; - - @JsonWrap("attributes") - private Date modified; + public Article(ObjectId id) { + this.id = id; + } @Override public ObjectId getId() { @@ -72,10 +36,6 @@ public class Article extends FileModel<ObjectId> implements Serializable { this.id = id; } - public void setId(String id) { - this.id = MongoUtils.objectId(id); - } - public String getTitle() { return title; } @@ -84,92 +44,4 @@ public class Article extends FileModel<ObjectId> implements Serializable { this.title = title; } - public String getText() { - return text; - } - - public void setText(String text) { - this.text = text; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public Date getDate() { - return date; - } - - public void setDate(Date date) { - this.date = date; - } - - public void setDate(String date) { - SimpleDateFormat df = new SimpleDateFormat(Constants.DATETIME_FORMAT); - try { - setDate(df.parse(date)); - } catch (ParseException e) {} - } - - public List<TopicRef> getTopics() { - return topics; - } - - public void setTopics(List<TopicRef> topics) { - this.topics = topics; - } - - public ArticleStats getStats() { - return stats; - } - - public void setStats(ArticleStats stats) { - this.stats = stats; - } - - public Date getCreated() { - return created; - } - - public void setCreated(Date created) { - this.created = created; - } - - public Date getModified() { - return modified; - } - - public void setModified(Date modified) { - this.modified = modified; - } - - @Override - public void fromFile(File file) throws IOException { - List<String> lines = FileUtils.readFile(file); - setTitle(lines.get(0)); - setText(StringUtils.join(lines.subList(1, lines.size()))); - } - - @Override - public String toFileString() { - return getTitle() + "\n" + getText(); - } - - @PrePersist - public void prePersist() { - this.modified = new Date(); - if (this.created == null) - this.created = modified; - } - - @Override - public String toString() { - return Article.class.getSimpleName() + "[id:" + id + ", title:" + title + ", url:" + url + ", date:" + date - + ", created:" + created + ", modified:" + modified + "]"; - } - -} \ No newline at end of file +} 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 new file mode 100644 index 00000000..6fe77f9c --- /dev/null +++ b/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java @@ -0,0 +1,175 @@ +package de.vipra.util.model; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +import org.bson.types.ObjectId; +import org.mongodb.morphia.annotations.Embedded; +import org.mongodb.morphia.annotations.Entity; +import org.mongodb.morphia.annotations.Id; +import org.mongodb.morphia.annotations.Index; +import org.mongodb.morphia.annotations.Indexes; +import org.mongodb.morphia.annotations.PrePersist; + +import de.vipra.util.Constants; +import de.vipra.util.FileUtils; +import de.vipra.util.MongoUtils; +import de.vipra.util.StringUtils; +import de.vipra.util.an.JsonType; +import de.vipra.util.an.JsonWrap; +import de.vipra.util.an.QueryIgnore; + +@SuppressWarnings("serial") +@JsonType("article") +@Entity(value = "articles", noClassnameStored = true) +@Indexes({ @Index("title"), @Index("date") }) +public class ArticleFull extends FileModel<ObjectId> implements Serializable { + + @Id + private ObjectId id; + + @JsonWrap("attributes") + private String title; + + @JsonWrap("attributes") + @QueryIgnore(multi = true) + private String text; + + @JsonWrap("attributes") + private String url; + + @JsonWrap("attributes") + private Date date; + + @Embedded + @JsonWrap("attributes") + @QueryIgnore(multi = true) + private List<TopicRef> topics; + + @Embedded + @JsonWrap("attributes") + @QueryIgnore(multi = true) + private ArticleStats stats; + + @JsonWrap("attributes") + private Date created; + + @JsonWrap("attributes") + private Date modified; + + @Override + public ObjectId getId() { + return id; + } + + @Override + public void setId(ObjectId id) { + this.id = id; + } + + public void setId(String id) { + this.id = MongoUtils.objectId(id); + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public void setDate(String date) { + SimpleDateFormat df = new SimpleDateFormat(Constants.DATETIME_FORMAT); + try { + setDate(df.parse(date)); + } catch (ParseException e) {} + } + + public List<TopicRef> getTopics() { + return topics; + } + + public void setTopics(List<TopicRef> topics) { + this.topics = topics; + } + + public ArticleStats getStats() { + return stats; + } + + public void setStats(ArticleStats stats) { + this.stats = stats; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + @Override + public void fromFile(File file) throws IOException { + List<String> lines = FileUtils.readFile(file); + setTitle(lines.get(0)); + setText(StringUtils.join(lines.subList(1, lines.size()))); + } + + @Override + public String toFileString() { + return getTitle() + "\n" + getText(); + } + + @PrePersist + public void prePersist() { + this.modified = new Date(); + if (this.created == null) + this.created = modified; + } + + @Override + public String toString() { + return ArticleFull.class.getSimpleName() + "[id:" + id + ", title:" + title + ", url:" + url + ", date:" + date + + ", created:" + created + ", modified:" + modified + "]"; + } + +} \ No newline at end of file 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 new file mode 100644 index 00000000..d5da6a81 --- /dev/null +++ b/vipra-util/src/main/java/de/vipra/util/model/Import.java @@ -0,0 +1,132 @@ +package de.vipra.util.model; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +import org.bson.types.ObjectId; +import org.mongodb.morphia.annotations.Entity; +import org.mongodb.morphia.annotations.Id; +import org.mongodb.morphia.annotations.Index; +import org.mongodb.morphia.annotations.Indexes; +import org.mongodb.morphia.annotations.PrePersist; +import org.mongodb.morphia.annotations.Reference; + +import de.vipra.util.an.JsonType; +import de.vipra.util.an.JsonWrap; +import de.vipra.util.an.QueryIgnore; + +@SuppressWarnings("serial") +@JsonType("import") +@Entity(value = "imports", noClassnameStored = true) +@Indexes({ @Index("date") }) +public class Import implements Model<ObjectId>, Serializable { + + @Id + private ObjectId id; + + @JsonWrap("attributes") + private Date date; + + @JsonWrap("attributes") + private long duration; + + @JsonWrap("attributes") + @QueryIgnore(multi = true) + @Reference(ignoreMissing = true) + private List<Article> articles; + + @JsonWrap("attributes") + private int articlesCount; + + @JsonWrap("attributes") + @QueryIgnore(multi = true) + @Reference(ignoreMissing = true) + private List<Topic> topics; + + @JsonWrap("attributes") + private int topicsCount; + + @JsonWrap("attributes") + @QueryIgnore(multi = true) + @Reference(ignoreMissing = true) + private List<Word> words; + + @JsonWrap("attributes") + private int wordsCount; + + @Override + public ObjectId getId() { + return id; + } + + @Override + public void setId(ObjectId id) { + this.id = id; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public long getDuration() { + return duration; + } + + public void setDuration(long duration) { + this.duration = duration; + } + + public List<Article> getArticles() { + return articles; + } + + public void setArticles(List<Article> articles) { + this.articles = articles; + if (articles != null) + articlesCount = articles.size(); + } + + public int getArticlesCount() { + return articlesCount; + } + + public List<Topic> getTopics() { + return topics; + } + + public void setTopics(List<Topic> topics) { + this.topics = topics; + if (topics != null) + topicsCount = topics.size(); + } + + public int getTopicsCount() { + return topicsCount; + } + + public List<Word> getWords() { + return words; + } + + public void setWords(List<Word> words) { + this.words = words; + if (words != null) + wordsCount = words.size(); + } + + public int getWordsCount() { + return wordsCount; + } + + @PrePersist + private void prePersist() { + if (date == null) + date = new Date(); + } + +} diff --git a/vipra-util/src/main/java/de/vipra/util/model/Topic.java b/vipra-util/src/main/java/de/vipra/util/model/Topic.java index 07568128..ca9ac7bb 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/Topic.java +++ b/vipra-util/src/main/java/de/vipra/util/model/Topic.java @@ -21,6 +21,12 @@ public class Topic implements Model<ObjectId>, Serializable { private ObjectId id; private String name; + public Topic() {} + + public Topic(ObjectId id) { + this.id = id; + } + @Override public ObjectId getId() { return id; 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 fa4bc349..fd0b6c1a 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 @@ -57,6 +57,11 @@ public class DatabaseService<T extends Model<?>, U> implements Service<T, U, Dat @Override public List<T> getMultiple(Integer skip, Integer limit, String sortBy, String... fields) { + return getMultiple(skip, limit, sortBy, true, fields); + } + + @Override + public List<T> getMultiple(Integer skip, Integer limit, String sortBy, boolean defaultIgnore, String... fields) { Query<T> q = datastore.createQuery(clazz); if (skip != null && skip > 0) q.offset(skip); @@ -66,7 +71,7 @@ public class DatabaseService<T extends Model<?>, U> implements Service<T, U, Dat q.order(sortBy); if (fields != null && fields.length > 0) q.retrievedFields(true, setMinus(fields, ignoredFieldsMulti)); - else if (ignoredFieldsMulti.length > 0) + else if (!defaultIgnore && ignoredFieldsMulti.length > 0) q.retrievedFields(false, ignoredFieldsMulti); List<T> list = q.asList(); return list; 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 b38d35af..95f6a6d0 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 @@ -10,6 +10,9 @@ public interface Service<Type extends Model<?>, 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) + throws E; + List<Type> getAll(String... fields) throws E; Type createSingle(Type t) throws E; -- GitLab