diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java index 162eb7f92a84b6e0e82acb3d82597cb82323120b..2fae2a246d87cf7e06d5184f87fdc7e2d5e98023 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java @@ -29,7 +29,7 @@ import de.vipra.util.ex.ConfigException; import de.vipra.util.model.ArticleFull; import de.vipra.util.model.TopicModel; import de.vipra.util.service.MongoService; -import de.vipra.util.service.Service.QueryBuilder; +import de.vipra.util.service.QueryBuilder; @Path("articles") public class ArticleResource { @@ -70,13 +70,13 @@ public class ArticleResource { query.fields(true, StringUtils.getFields(fields)); if (topicModel != null && !topicModel.isEmpty()) - query.criteria("topicModel", new TopicModel(topicModel)); + query.eq("topicModel", new TopicModel(topicModel)); if (word != null && !word.isEmpty()) - query.criteria("words.id", word); + query.eq("words.id", word); if (entity != null && !entity.isEmpty()) - query.criteria("entities.entity.id", entity); + query.eq("entities.entity.id", entity); final List<ArticleFull> articles = dbArticles.getMultiple(query); diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/BugReportResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/BugReportResource.java index bd5e0bd12c65f32bc716e3117d7f1b3f54680f84..3f73822f626c5fb9d07fed6f8a173657a056db3b 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/resource/BugReportResource.java +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/BugReportResource.java @@ -31,7 +31,7 @@ import de.vipra.util.ex.ConfigException; import de.vipra.util.ex.DatabaseException; import de.vipra.util.model.BugReport; import de.vipra.util.service.MongoService; -import de.vipra.util.service.Service.QueryBuilder; +import de.vipra.util.service.QueryBuilder; @Path("bugreports") public class BugReportResource { diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/SequenceResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/SequenceResource.java index aafcb515d138d18e79a4e44329651d130aab242d..1f491b92782b79c6d99739ba0bc376db379f8f26 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/resource/SequenceResource.java +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/SequenceResource.java @@ -26,7 +26,7 @@ import de.vipra.util.ex.ConfigException; import de.vipra.util.model.SequenceFull; import de.vipra.util.model.TopicModel; import de.vipra.util.service.MongoService; -import de.vipra.util.service.Service.QueryBuilder; +import de.vipra.util.service.QueryBuilder; @Path("sequences") public class SequenceResource { @@ -54,7 +54,7 @@ public class SequenceResource { query.fields(true, StringUtils.getFields(fields)); if (topicModel != null && !topicModel.isEmpty()) - query.criteria("topicModel", new TopicModel(topicModel)); + query.eq("topicModel", new TopicModel(topicModel)); final List<SequenceFull> sequences = dbSequences.getMultiple(query); diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/TextEntityResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/TextEntityResource.java index 25c59ed4a0ce02d1b0cd303f26a4bcbf63118ec3..cbb74e613ca99d5672392c121174f5b2f892d3c7 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/resource/TextEntityResource.java +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/TextEntityResource.java @@ -25,7 +25,7 @@ import de.vipra.util.ex.ConfigException; import de.vipra.util.model.TextEntityFull; import de.vipra.util.model.TopicModel; import de.vipra.util.service.MongoService; -import de.vipra.util.service.Service.QueryBuilder; +import de.vipra.util.service.QueryBuilder; @Path("entities") public class TextEntityResource { @@ -56,7 +56,7 @@ public class TextEntityResource { query.fields(true, StringUtils.getFields(fields)); if (topicModel != null && !topicModel.isEmpty()) - query.criteria("topicModel", new TopicModel(topicModel)); + query.eq("topicModel", new TopicModel(topicModel)); final List<TextEntityFull> entities = dbEntities.getMultiple(query); diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/TopicModelResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/TopicModelResource.java index 50b35a6f2ddadfd804851517d013b3afff03e94c..c28eafebbaf9613fd900804a47be0659f041017f 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/resource/TopicModelResource.java +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/TopicModelResource.java @@ -22,7 +22,7 @@ import de.vipra.util.StringUtils; import de.vipra.util.ex.ConfigException; import de.vipra.util.model.TopicModelFull; import de.vipra.util.service.MongoService; -import de.vipra.util.service.Service.QueryBuilder; +import de.vipra.util.service.QueryBuilder; @Path("topicmodels") public class TopicModelResource { diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/TopicResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/TopicResource.java index 70849ce76ee4b9f96c00bc5b7cc595684de2439f..794a86310c074edc3967bd2091935a6637191598 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/resource/TopicResource.java +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/TopicResource.java @@ -32,7 +32,7 @@ import de.vipra.util.model.Topic; import de.vipra.util.model.TopicFull; import de.vipra.util.model.TopicModel; import de.vipra.util.service.MongoService; -import de.vipra.util.service.Service.QueryBuilder; +import de.vipra.util.service.QueryBuilder; @Path("topics") public class TopicResource { @@ -62,10 +62,10 @@ public class TopicResource { query.fields(true, StringUtils.getFields(fields)); if (topicModel != null && !topicModel.isEmpty()) - query.criteria("topicModel", new TopicModel(topicModel)); + query.eq("topicModel", new TopicModel(topicModel)); if (word != null && !word.isEmpty()) - query.criteria("words.id", word); + query.eq("words.id", word); final List<TopicFull> topics = dbTopics.getMultiple(query); @@ -118,14 +118,14 @@ public class TopicResource { final ResponseWrapper<List<ArticleFull>> res = new ResponseWrapper<>(); try { final Topic topic = new Topic(MongoUtils.objectId(id)); - final QueryBuilder query = QueryBuilder.builder().criteria("topics.topic", topic).skip(skip).limit(limit).sortBy(sortBy); + final QueryBuilder query = QueryBuilder.builder().eq("topics.topic", topic).skip(skip).limit(limit).sortBy(sortBy); if (fields != null && !fields.isEmpty()) query.fields(true, StringUtils.getFields(fields)); final List<ArticleFull> articles = dbArticles.getMultiple(query); if ((skip != null && skip > 0) || (limit != null && limit > 0)) - res.addHeader("total", dbArticles.count(QueryBuilder.builder().criteria("topics.topic", topic))); + res.addHeader("total", dbArticles.count(QueryBuilder.builder().eq("topics.topic", topic))); else res.addHeader("total", articles.size()); diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/WindowResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/WindowResource.java index 7d1555c7c75a1d05aaf8772da2bc46c91037644e..3577e152adbaa5015bb7fcb567bb26f7dce0aa87 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/resource/WindowResource.java +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/WindowResource.java @@ -21,7 +21,7 @@ import de.vipra.util.ex.ConfigException; import de.vipra.util.model.TopicModel; import de.vipra.util.model.WindowFull; import de.vipra.util.service.MongoService; -import de.vipra.util.service.Service.QueryBuilder; +import de.vipra.util.service.QueryBuilder; @Path("windows") public class WindowResource { @@ -49,7 +49,7 @@ public class WindowResource { query.fields(true, StringUtils.getFields(fields)); if (topicModel != null && !topicModel.isEmpty()) - query.criteria("topicModel", new TopicModel(topicModel)); + query.eq("topicModel", new TopicModel(topicModel)); final List<WindowFull> windows = dbWindows.getMultiple(query); diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/WordResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/WordResource.java index a4247aacfb48c6f844c49823bf8cee08c7af7c2c..572e2a8e6d203c100e1d1843c922b952ca6628c9 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/resource/WordResource.java +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/WordResource.java @@ -24,7 +24,7 @@ import de.vipra.util.ex.ConfigException; import de.vipra.util.model.TopicModel; import de.vipra.util.model.WordFull; import de.vipra.util.service.MongoService; -import de.vipra.util.service.Service.QueryBuilder; +import de.vipra.util.service.QueryBuilder; @Path("words") public class WordResource { @@ -52,7 +52,7 @@ public class WordResource { query.fields(true, StringUtils.getFields(fields)); if (topicModel != null && !topicModel.isEmpty()) - query.criteria("topicModel", new TopicModel(topicModel)); + query.eq("topicModel", new TopicModel(topicModel)); final List<WordFull> words = dbWords.getMultiple(query); diff --git a/vipra-cmd/runcfg/CMD.launch b/vipra-cmd/runcfg/CMD.launch index b86e653f24302a285881641370a4b79b898294ce..a9b34fe31e519e52254a047acf6ff5f98955f666 100644 --- a/vipra-cmd/runcfg/CMD.launch +++ b/vipra-cmd/runcfg/CMD.launch @@ -11,7 +11,7 @@ </listAttribute> <stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.m2e.launchconfig.classpathProvider"/> <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="de.vipra.cmd.Main"/> -<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-C yearly"/> +<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-dD reuters -C reuters -S reuters -I /home/eike/repos/master/ma-impl/vm/data/reuters.json"/> <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="vipra-cmd"/> <stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.m2e.launchconfig.sourcepathProvider"/> <stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea"/> diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/CommandLineOptions.java b/vipra-cmd/src/main/java/de/vipra/cmd/CommandLineOptions.java index bdf43b154da810b11bff48cf5611ccc8e7796273..f75c615d0a3bbe569be0b0f8cac50905180f6fa8 100644 --- a/vipra-cmd/src/main/java/de/vipra/cmd/CommandLineOptions.java +++ b/vipra-cmd/src/main/java/de/vipra/cmd/CommandLineOptions.java @@ -22,8 +22,9 @@ public class CommandLineOptions { public static final Option DELETE = Option.builder("D").longOpt("delete").desc("delete existing models").hasArgs().argName("models...").build(); public static final Option EDIT = Option.builder("E").longOpt("edit").desc("edit config of selected models").hasArgs().argName("models...") .build(); + public static final Option PRINT = Option.builder("p").longOpt("print").desc("print model configuration").hasArgs().argName("models...").build(); public static final Option IMPORT = Option.builder("I").longOpt("import").desc("import data from json into selected models").hasArgs() - .argName("models...").build(); + .argName("files...").build(); public static final Option MODEL = Option.builder("M").longOpt("model").desc("generate topics on selected models").build(); public static final Option SELECT = Option.builder("S").longOpt("select").desc("select models").hasArgs().argName("models...").build(); @@ -32,7 +33,8 @@ public class CommandLineOptions { private final String cmdName = "vipra"; public CommandLineOptions() { - final Option[] optionsArray = { CLEAR, DEBUG, HELP, INDEX, LIST, REREAD, SILENT, TEST, ALL, CREATE, DELETE, EDIT, IMPORT, MODEL, SELECT }; + final Option[] optionsArray = { CLEAR, DEBUG, HELP, INDEX, LIST, REREAD, SILENT, TEST, ALL, CREATE, DELETE, EDIT, PRINT, IMPORT, MODEL, + SELECT }; options = new Options(); for (final Option option : optionsArray) options.addOption(option); @@ -127,6 +129,14 @@ public class CommandLineOptions { return getOptionValues(EDIT); } + public boolean isPrint() { + return hasOption(PRINT); + } + + public String[] modelsToPrint() { + return getOptionValues(PRINT); + } + public boolean isImport() { return hasOption(IMPORT); } diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/Main.java b/vipra-cmd/src/main/java/de/vipra/cmd/Main.java index 145b7965aa6efde2072a59a8d7ef0eb906c86a7b..e6cb162db1b4ece5987e89c931e5f67844825743 100644 --- a/vipra-cmd/src/main/java/de/vipra/cmd/Main.java +++ b/vipra-cmd/src/main/java/de/vipra/cmd/Main.java @@ -17,6 +17,7 @@ import de.vipra.cmd.option.ImportCommand; import de.vipra.cmd.option.IndexingCommand; import de.vipra.cmd.option.ListModelsCommand; import de.vipra.cmd.option.ModelingCommand; +import de.vipra.cmd.option.PrintModelCommand; import de.vipra.cmd.option.TestCommand; import de.vipra.util.ConsoleUtils; import de.vipra.util.ex.ConfigException; @@ -59,18 +60,21 @@ public class Main { if (opts.isClear()) commands.add(new ClearCommand()); - if (opts.isCreate()) - commands.add(new CreateModelCommand(opts.modelsToCreate())); - if (opts.isDelete()) commands.add(new DeleteModelCommand(opts.modelsToDelete())); + if (opts.isCreate()) + commands.add(new CreateModelCommand(opts.modelsToCreate())); + if (opts.isList()) commands.add(new ListModelsCommand()); if (opts.isEdit()) commands.add(new EditModelCommand(opts.modelsToEdit())); + if (opts.isPrint()) + commands.add(new PrintModelCommand(opts.modelsToPrint())); + if (opts.isImport()) commands.add(new ImportCommand(opts.selectedModels(), opts.filesToImport())); diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java b/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java index e8df881a54ef1fdc9f2b77d372a588689981a21e..7f331e871702edced1adcf323c0c3d3448366ecd 100644 --- a/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java +++ b/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java @@ -42,7 +42,7 @@ import de.vipra.util.model.TopicWord; import de.vipra.util.model.Window; import de.vipra.util.model.WindowFull; import de.vipra.util.service.MongoService; -import de.vipra.util.service.Service.QueryBuilder; +import de.vipra.util.service.QueryBuilder; public class Analyzer { @@ -122,9 +122,8 @@ public class Analyzer { long lastStarted = System.nanoTime(); long lastDuration = 0; double avgDuration = 0; - final double smoothingFactor = 0.005; - - printProgress(0, 0, iteration, maxIterationsLength, 0, modelConfig); + final double smoothingFactor = 0.1; + int lastLength = printProgress(0, 0, iteration, maxIterationsLength, 0, modelConfig, 0); while ((line = in.readLine()) != null) { if (line.contains("EM iter")) { @@ -143,7 +142,7 @@ public class Analyzer { avgDuration = smoothingFactor * lastDuration + (1 - smoothingFactor) * avgDuration; final long remainingDuration = (long) avgDuration * (modelConfig.getDynamicMaxIterations() - iteration); - printProgress(tenthPercent, progress, iteration, maxIterationsLength, remainingDuration, modelConfig); + lastLength = printProgress(tenthPercent, progress, iteration, maxIterationsLength, remainingDuration, modelConfig, lastLength); } } @@ -287,7 +286,6 @@ public class Analyzer { // create sequence final SequenceFull newSequenceFull = new SequenceFull(); newSequenceFull.setWindow(new Window(newWindows.get(idxSeq))); - Collections.sort(newSequenceWords, Comparator.reverseOrder()); newSequenceFull.setWords(newSequenceWords); newSequenceFull.setRelevance(relevance); newSequenceFull.setRelevanceChange(relevance - prevRelevance); @@ -441,7 +439,7 @@ public class Analyzer { // recreate entities - final QueryBuilder builder = QueryBuilder.builder().criteria("topicModel", new TopicModel(modelConfig.getName())); + final QueryBuilder builder = QueryBuilder.builder().eq("topicModel", new TopicModel(modelConfig.getName())); dbWindows.deleteMultiple(builder); dbSequences.deleteMultiple(builder); @@ -455,12 +453,14 @@ public class Analyzer { dbTopicModels.replaceSingle(topicModel); } - private void printProgress(final int tenthPercent, final double progress, final int iteration, final int maxIterationsLength, - final long remainingNanos, final TopicModelConfig modelConfig) { - ConsoleUtils.infoNOLF(" [" + StringUtils.repeat("#", tenthPercent) + StringUtils.repeat(" ", 10 - tenthPercent) + "] " + private int printProgress(final int tenthPercent, final double progress, final int iteration, final int maxIterationsLength, + final long remainingNanos, final TopicModelConfig modelConfig, final int lastLength) { + String msg = " [" + StringUtils.repeat("#", tenthPercent) + StringUtils.repeat(" ", 10 - tenthPercent) + "] " + StringUtils.pad(Integer.toString((int) Math.floor(progress)), 3, true) + "% (" + StringUtils.pad(Integer.toString(iteration), maxIterationsLength, true) + "/" + modelConfig.getDynamicMinIterations() + "-" - + modelConfig.getDynamicMaxIterations() + ")\r"); + + modelConfig.getDynamicMaxIterations() + ") " + StringUtils.timeString(remainingNanos, false, true, false) + "\r"; + ConsoleUtils.infoNOLF(msg); + return msg.length() - 1; } } diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/DeleteModelCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/DeleteModelCommand.java index 00114a421d9d000ae8b43d8e8fda2c7aef5047e2..f54b707c8d8709324516d4f2e5c36c2b4631894f 100644 --- a/vipra-cmd/src/main/java/de/vipra/cmd/option/DeleteModelCommand.java +++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/DeleteModelCommand.java @@ -15,7 +15,7 @@ import de.vipra.util.model.TopicModel; import de.vipra.util.model.WindowFull; import de.vipra.util.model.WordFull; import de.vipra.util.service.MongoService; -import de.vipra.util.service.Service.QueryBuilder; +import de.vipra.util.service.QueryBuilder; public class DeleteModelCommand implements Command { @@ -47,7 +47,7 @@ public class DeleteModelCommand implements Command { dbTopicModels.deleteMultiple(Arrays.asList(names)); for (final String name : names) { - final QueryBuilder builder = QueryBuilder.builder().criteria("topicModel", new TopicModel(name)); + final QueryBuilder builder = QueryBuilder.builder().eq("topicModel", new TopicModel(name)); dbArticles.deleteMultiple(builder); dbTopics.deleteMultiple(builder); dbWindows.deleteMultiple(builder); 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 4391d21ccf204a1232825318f5ecc8d829c945c5..4d1fa20312b787197d9d52a156224e93919f77b2 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 @@ -155,31 +155,36 @@ public class ImportCommand implements Command { // preprocess text final ProcessedText processedText = processor.process(modelConfig, article.getText()); - // spotlight analysis - if (spotlightAnalyzer != null) { - final SpotlightResponse spotlightResponse = spotlightAnalyzer.analyze(article.getText()); - - String articleText = article.getText(); - - for (final TextEntityCount textEntityCount : spotlightResponse.getEntities()) { - // get new text entity - final TextEntityFull newTextEntity = new TextEntityFull(textEntityCount.getEntity()); - newTextEntity.setTopicModel(new TopicModel(topicModel.getId())); - newTextEntities.add(newTextEntity); - - // insert entity into text - articleText = articleText.replaceAll("(?i)\\b" + Pattern.quote(textEntityCount.getEntity().getId()) + "\\b(?![^<]*>|[^<>]*</)", - Matcher.quoteReplacement(textEntityCount.getEntity().aTag())); - } - - article.setEntities(spotlightResponse.getEntities()); - article.setText(articleText); - } - if (modelConfig.getProcessorMode() != ProcessorMode.ENTITIES && processedText.getReducedWordCount() < modelConfig.getDocumentMinimumLength()) { - ConsoleUtils.info(ConsoleUtils.positionString(current, max) + " skipped \"" + object.get("title")); + ConsoleUtils.info(ConsoleUtils.positionString(current, max) + " skipped \"" + object.get("title") + "\" (" + + processedText.getReducedWordCount() + ")"); } else { + // spotlight analysis + if (spotlightAnalyzer != null) { + final SpotlightResponse spotlightResponse = spotlightAnalyzer.analyze(article.getText()); + + String articleText = article.getText(); + + final List<TextEntityCount> textEntitiesCounts = spotlightResponse.getEntities(); + if (textEntitiesCounts != null) { + for (final TextEntityCount textEntityCount : textEntitiesCounts) { + // get new text entity + final TextEntityFull newTextEntity = new TextEntityFull(textEntityCount.getEntity()); + newTextEntity.setTopicModel(new TopicModel(topicModel.getId())); + newTextEntities.add(newTextEntity); + + // insert entity into text + articleText = articleText.replaceAll( + "(?i)\\b" + Pattern.quote(textEntityCount.getEntity().getId()) + "\\b(?![^<]*>|[^<>]*</)", + Matcher.quoteReplacement(textEntityCount.getEntity().aTag())); + } + } + + article.setEntities(textEntitiesCounts); + article.setText(articleText); + } + article.setProcessedText(processedText.getWords()); article.setWords(processedText.getArticleWords()); article.setTopicModel(new TopicModel(topicModel.getId())); diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/PrintModelCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/PrintModelCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..e315b3c080d2484c9d06f7909f5133fa4645ebd4 --- /dev/null +++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/PrintModelCommand.java @@ -0,0 +1,29 @@ +package de.vipra.cmd.option; + +import de.vipra.util.Config; +import de.vipra.util.ConsoleUtils; +import de.vipra.util.model.TopicModelConfig; + +public class PrintModelCommand implements Command { + + private final String[] names; + private Config config; + + public PrintModelCommand(final String[] names) { + this.names = names; + } + + private void printModelConfig(final String name, final TopicModelConfig modelConfig) { + ConsoleUtils.info("model: " + name + "\n" + modelConfig.toPrettyString()); + } + + @Override + public void run() throws Exception { + config = Config.getConfig(); + + for (final String name : names) { + printModelConfig(name, config.getTopicModelConfig(name)); + } + } + +} diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/text/SpotlightResponse.java b/vipra-cmd/src/main/java/de/vipra/cmd/text/SpotlightResponse.java index 19f2cc4024f43c8add24200bdbc6266813050a30..646fddfa71f169da6cb2307585e4578edc29d51e 100644 --- a/vipra-cmd/src/main/java/de/vipra/cmd/text/SpotlightResponse.java +++ b/vipra-cmd/src/main/java/de/vipra/cmd/text/SpotlightResponse.java @@ -29,6 +29,8 @@ public class SpotlightResponse { } public List<TextEntityCount> getEntities() { + if (resources == null) + return new ArrayList<>(); final CountMap<String> textEntitiesCount = new CountMap<>(resources.size()); final Set<TextEntity> textEntities = new HashSet<>(resources.size()); diff --git a/vipra-ui/app/html/articles/show.html b/vipra-ui/app/html/articles/show.html index efc9fb98b732053dfedd9a4c88a8e03cec78773a..470ba5bb6fcd3d118d17827cafa644ae2abdd436 100644 --- a/vipra-ui/app/html/articles/show.html +++ b/vipra-ui/app/html/articles/show.html @@ -34,7 +34,7 @@ <th>Date</th> <td ng-bind="::articleDate"></td> </tr> - <tr> + <tr ng-if="article.url"> <th>URL</th> <td> <a ng-href="{{::article.url}}" target="_blank"> @@ -74,7 +74,7 @@ </div> <div class="col-md-4"> <h3>Share</h3> - <div class="pie-chart" id="topic-share" highcharts="topicShare" style="height: 200px;"></div> + <div class="pie-chart" id="topic-share" highcharts="topicShare" ng-class="{'pie-small':!article.topics.length}"></div> </div> </div> <h3>Similar articles</h3> diff --git a/vipra-ui/app/html/directives/window-dropdown.html b/vipra-ui/app/html/directives/window-dropdown.html new file mode 100644 index 0000000000000000000000000000000000000000..69557e060f80230635613888f50b2a5655a25e6e --- /dev/null +++ b/vipra-ui/app/html/directives/window-dropdown.html @@ -0,0 +1,5 @@ +<ol class="nya-bs-select nya-bs-condensed" ng-model="ngModel" ng-class="{dropup:dropup}"> + <li value="{{window.id}}" class="nya-bs-option" ng-repeat="window in windows"> + <a ng-bind="window.label"></a> + </li> +</ol> diff --git a/vipra-ui/app/html/network.html b/vipra-ui/app/html/network.html index 98282f1d1436d57d272c0e1692e5b80aa1093b12..a5bdda9194d9299e62fe31d176e51a91738c6881 100644 --- a/vipra-ui/app/html/network.html +++ b/vipra-ui/app/html/network.html @@ -2,13 +2,17 @@ <div class="fullsize navpadding"> <div class="graph-legend overlay"> <div class="checkbox"> - <input type="checkbox" id="showArticles" ng-model="shown.articles" ng-disabled="type == 'articles'"> + <input type="checkbox" id="showArticles" ng-model="shown.articles"> <label for="showArticles" style="color:{{colors.articles}}">Articles</label> </div> <div class="checkbox"> - <input type="checkbox" id="showTopics" ng-model="shown.topics" ng-disabled="type == 'topics'"> + <input type="checkbox" id="showTopics" ng-model="shown.topics"> <label for="showTopics" style="color:{{colors.topics}}">Topics</label> </div> + <div class="checkbox"> + <input type="checkbox" id="showWords" ng-model="shown.words"> + <label for="showWords">Words</label> + </div> </div> <div class="fullsize" id="visgraph"></div> </div> diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js index dff06132ecf77e8bf996be4e96d0db6279475911..33a80659ad304a1abf418d622e17095732ad5a86 100644 --- a/vipra-ui/app/js/controllers.js +++ b/vipra-ui/app/js/controllers.js @@ -239,8 +239,8 @@ /** * Network controller */ - app.controller('NetworkController', ['$scope', '$state', '$stateParams', '$timeout', 'ArticleFactory', 'TopicFactory', - function($scope, $state, $stateParams, $timeout, ArticleFactory, TopicFactory) { + app.controller('NetworkController', ['$scope', '$state', '$stateParams', '$timeout', 'ArticleFactory', 'TopicFactory', 'WordFactory', 'WindowFactory', + function($scope, $state, $stateParams, $timeout, ArticleFactory, TopicFactory, WordFactory, WindowFactory) { var id = 0, ids = {}, @@ -248,7 +248,8 @@ $scope.colors = { articles: '#BBC9D2', - topics: '#DBB234' + topics: '#DBB234', + words: '#FFFFFF' }; $scope.nodes = new vis.DataSet(); $scope.edges = new vis.DataSet(); @@ -263,7 +264,7 @@ size: 14 }, shape: 'dot', - borderWidth: 0 + borderWidth: 1 }, edges: { color: { @@ -282,7 +283,8 @@ }; $scope.shown = { articles: true, - topics: true + topics: true, + words: true }; var factory; @@ -290,6 +292,8 @@ factory = ArticleFactory; else if ($stateParams.type === 'topics') factory = TopicFactory; + else if ($stateParams.type === 'word') + factory = WordFactory; else { console.log('unknown network type'); return; @@ -311,6 +315,8 @@ $scope.nodes.add([articleNode(data)]); else if ($stateParams.type === 'topics') $scope.nodes.add([topicNode(data)]); + else if ($stateParams.type === 'words') + $scope.nodes.add([wordNode(data)]); ids[data.id] = id; // create graph @@ -329,7 +335,7 @@ return { id: id, title: title, - label: title.multiline(5), + label: title.multiline(5).ellipsize(50), type: type, show: show, dbid: dbid, @@ -352,6 +358,10 @@ return newNode(article.title, 'article', 'articles.show', article.id, $scope.colors.articles, 'square'); }; + var wordNode = function(word) { + return newNode(word.id, 'word', 'word.show', word.id, $scope.colors.words, 'box'); + }; + var edgeExists = function(idA, idB) { if (idB < idA) { var tmp = idA; @@ -409,30 +419,65 @@ var node = $scope.nodes.get(props.nodes[0]); if (node) { if (node.type === 'article' && $scope.shown.topics) { - // node is article, load article to get topics - ArticleFactory.get({ - id: node.dbid - }, function(data) { - if (data.topics) { - for (var i = 0; i < data.topics.length; i++) - data.topics[i] = data.topics[i].topic; - constructor(data.topics, node, topicNode); - } - }); + $scope.loadArticle(node); } else if (node.type === 'topic') { - // node is topic, load topic to get articles - if ($scope.shown.articles) - TopicFactory.articles({ - id: node.dbid - }, function(data) { - constructor(data, node, articleNode); - }); + $scope.loadTopic(node); + } else if (node.type === 'word') { + $scope.loadWord(node); } $scope.nodes.update(node); } }, 500); }; + $scope.loadArticle = function(node) { + ArticleFactory.get({ + id: node.dbid + }, function(data) { + if (data.topics) { + for (var i = 0; i < data.topics.length; i++) + data.topics[i] = data.topics[i].topic; + constructor(data.topics, node, topicNode); + } + }); + }; + + $scope.loadTopic = function(node) { + if ($scope.shown.articles) { + TopicFactory.articles({ + id: node.dbid + }, function(data) { + constructor(data, node, articleNode); + }); + } + if ($scope.shown.words) { + TopicFactory.get({ + id: node.dbid + }, function(data) { + constructor(data.words, node, wordNode); + }); + } + }; + + $scope.loadWord = function(node) { + if($scope.shown.articles) { + ArticleFactory.query({ + word: node.dbid, + topicModel: $scope.rootModels.topicModel.id + }, function(data) { + constructor(data, node, articleNode); + }); + } + if($scope.shown.topics) { + TopicFactory.query({ + word: node.dbid, + topicModel: $scope.rootModels.topicModel.id + }, function(data) { + constructor(data, node, topicNode); + }); + } + }; + // on node open $scope.open = function(props) { $timeout.cancel(selectTimeout); @@ -602,6 +647,12 @@ $scope.$watch('explorerModels.sorttopics', function() { if (!$scope.topics) return; + if($scope.explorerModels.sorttopics === 'name') { + $scope.explorerModels.sortdir = false; + } else { + $scope.explorerModels.sortdir = true; + } + $timeout(function() { for (var i = 0; i < $scope.topics.length; i++) $scope.topics[i].topicCurrValue = $scope.topicCurrValue($scope.topics[i]); diff --git a/vipra-ui/app/js/directives.js b/vipra-ui/app/js/directives.js index 80b1669746e500606226a9be5bdbacc89edcf9fc..46a5a8f1cdc5a2ce11b2918a84d7c675b0bf9783 100644 --- a/vipra-ui/app/js/directives.js +++ b/vipra-ui/app/js/directives.js @@ -258,7 +258,7 @@ if (newValue) { for (var i = 0, s; i < $scope.sequences.length; i++) { s = $scope.sequences[i]; - s.label = Vipra.sequenceLabel(s.window.startDate, s.window.windowResolution); + s.label = Vipra.windowLabel(s.window.startDate, s.window.windowResolution); } } }); @@ -267,6 +267,31 @@ }; }]); + app.directive('windowDropdown', ['WindowFactory', function(WindowFactory) { + return { + scope: { + ngModel: '=', + topicModel: '=', + dropup: '@' + }, + link: function($scope) { + $scope.dropup = $scope.dropup === 'true'; + $scope.$watch('topicModel', function(value) { + if(value) { + WindowFactory.query({ + topicModel: value.id + }, function(data) { + for(var i = 0; i < data.length; i++) + data.label = Vipra.windowLabel(data[i].startDate, data[i].windowResolution); + $scope.windows = data; + }); + } + }); + }, + templateUrl: '/html/directives/window-dropdown.html' + }; + }]); + app.directive('sortBy', [function() { return { restrict: 'A', diff --git a/vipra-ui/app/js/helpers.js b/vipra-ui/app/js/helpers.js index 8d58156a70c4dfe3afd49b2ab07f683a140fc9bf..c3e02bbfa25b64f56da960c65f83525bd889c01c 100644 --- a/vipra-ui/app/js/helpers.js +++ b/vipra-ui/app/js/helpers.js @@ -38,7 +38,7 @@ return 'id' + Math.random().toString(36).substring(7); }; - Vipra.sequenceLabel = function(date, res) { + Vipra.windowLabel = function(date, res) { date = moment(date); var parts = []; if (res === 'QUARTER') { @@ -177,6 +177,11 @@ return this.split(new RegExp("((?:\\w+ ){" + max + "})", "g")).filter(Boolean).join("\n"); }; + if (typeof String.prototype.ellipsize === 'undefined') + String.prototype.ellipsize = function(max) { + return this.length <= max ? this : this.substring(0, max) + '...'; + }; + if (typeof String.prototype.startsWith === 'undefined') String.prototype.startsWith = function(start) { return this.lastIndexOf(start, 0) === 0; diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less index efe492d576366ce0ba8313e707a069464dd1553e..54b73042d6f20d560f0476d7ddc11d4249fe0b01 100644 --- a/vipra-ui/app/less/app.less +++ b/vipra-ui/app/less/app.less @@ -498,6 +498,14 @@ entity-menu { } } +.pie-chart { + height: 220px; + + &.pie-small { + height: 180px; + } +} + @-moz-keyframes spin { 100% { -moz-transform: rotateY(360deg); 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 d8b6d69b726dcd158e1852b984b7048321f38303..c903def65e18de28017f25574a023bad83c18745 100644 --- a/vipra-util/src/main/java/de/vipra/util/StringUtils.java +++ b/vipra-util/src/main/java/de/vipra/util/StringUtils.java @@ -50,13 +50,15 @@ public class StringUtils { return join(Arrays.asList(arr), separator, reverse); } - public static String timeString(long nanos, final boolean showMillis, final boolean compactHMS) { + public static String timeString(long nanos, final boolean showMillis, final boolean compactHMS, final boolean onlyHMS) { final List<String> parts = new ArrayList<String>(6); - final long days = TimeUnit.NANOSECONDS.toDays(nanos); - if (days > 0) { - parts.add(days + "d"); - nanos -= TimeUnit.DAYS.toNanos(days); + if (!onlyHMS) { + final long days = TimeUnit.NANOSECONDS.toDays(nanos); + if (days > 0) { + parts.add(days + "d"); + nanos -= TimeUnit.DAYS.toNanos(days); + } } final long hours = TimeUnit.NANOSECONDS.toHours(nanos); @@ -89,7 +91,7 @@ public class StringUtils { nanos -= TimeUnit.SECONDS.toNanos(seconds); } - if (showMillis) { + if (showMillis && !onlyHMS) { final long millis = TimeUnit.NANOSECONDS.toMillis(nanos); if (millis > 0 || compactHMS) { if (compactHMS) { @@ -108,11 +110,11 @@ public class StringUtils { } public static String timeString(final long nanos, final boolean showMillis) { - return timeString(nanos, showMillis, false); + return timeString(nanos, showMillis, false, false); } public static String timeString(final long nanos) { - return timeString(nanos, true, false); + return timeString(nanos, true, false, false); } public static String padNumber(final long number, final int length) { diff --git a/vipra-util/src/main/java/de/vipra/util/Tuple.java b/vipra-util/src/main/java/de/vipra/util/Tuple.java deleted file mode 100644 index 47613e2f0fd86a184c983b9d35e231d255ee4b1f..0000000000000000000000000000000000000000 --- a/vipra-util/src/main/java/de/vipra/util/Tuple.java +++ /dev/null @@ -1,35 +0,0 @@ -package de.vipra.util; - -public class Tuple<X, Y> { - - private X first; - private Y second; - - public Tuple() {} - - public Tuple(final X first, final Y second) { - this.first = first; - this.second = second; - } - - public X first() { - return first; - } - - public void setFirst(final X first) { - this.first = first; - } - - public Y second() { - return second; - } - - public void setSecond(final Y second) { - this.second = second; - } - - public static <X, Y> Tuple<X, Y> pair(final X first, final Y second) { - return new Tuple<>(first, second); - } - -} diff --git a/vipra-util/src/main/java/de/vipra/util/model/TopicModelConfig.java b/vipra-util/src/main/java/de/vipra/util/model/TopicModelConfig.java index 7e8424012bd4bde153fa8b6d69d7242ce65aecdc..aa6fe9f1ef309faa96280b6150451df258703ca3 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/TopicModelConfig.java +++ b/vipra-util/src/main/java/de/vipra/util/model/TopicModelConfig.java @@ -241,4 +241,14 @@ public class TopicModelConfig implements Serializable { + dynamicMinIterations + "-" + dynamicMaxIterations + "]"; } + public String toPrettyString() { + return " kTopics: " + kTopics + "\n kTopWords: " + kTopWords + "\n dynamicMinIterations: " + dynamicMinIterations + + "\n dynamicMaxIterations: " + dynamicMaxIterations + "\n staticIterations: " + staticIterations + "\n topicAutoNamingWords: " + + topicAutoNamingWords + "\n maxSimilarDocuments: " + maxSimilarDocuments + "\n documentMinimumLength: " + documentMinimumLength + + "\n documentMinimumWordFrequency: " + documentMinimumWordFrequency + "\n spotlightSupport: " + spotlightSupport + + "\n spotlightConfidence: " + spotlightConfidence + "\n minTopicShare: " + minTopicShare + "\n minRelativeProbability: " + + minRelativeProbability + "\n risingDecayLambda: " + risingDecayLambda + "\n maxSimilarDocumentsDivergence: " + + maxSimilarDocumentsDivergence + "\n windowResolution: " + windowResolution + "\n processorMode: " + processorMode; + } + } diff --git a/vipra-util/src/main/java/de/vipra/util/service/MongoService.java b/vipra-util/src/main/java/de/vipra/util/service/MongoService.java index 0462fafe3c3fea96342deee2506fea73c133d301..b0c10474286260e0418b5bc50dc982858107d1ed 100644 --- a/vipra-util/src/main/java/de/vipra/util/service/MongoService.java +++ b/vipra-util/src/main/java/de/vipra/util/service/MongoService.java @@ -13,14 +13,13 @@ import org.mongodb.morphia.query.UpdateOperations; import de.vipra.util.Config; import de.vipra.util.ListUtils; import de.vipra.util.Mongo; -import de.vipra.util.Tuple; import de.vipra.util.an.QueryIgnore; import de.vipra.util.an.UpdateIgnore; import de.vipra.util.ex.ConfigException; import de.vipra.util.ex.DatabaseException; import de.vipra.util.model.Model; -public class MongoService<Type extends Model<IdType>, IdType> implements Service<Type, IdType, DatabaseException> { +public class MongoService<Type extends Model<IdType>, IdType> { private final Datastore datastore; private final Class<Type> clazz; @@ -56,12 +55,10 @@ public class MongoService<Type extends Model<IdType>, IdType> implements Service this.ignoredFieldsMultiQuery = ignoreMulti.toArray(new String[ignoreMulti.size()]); } - @Override public Type getSingle(final IdType id, final String... fields) throws DatabaseException { return getSingle(id, QueryBuilder.builder().fields(true, fields)); } - @Override public Type getSingle(final IdType id, final QueryBuilder builder) throws DatabaseException { final Query<Type> query = datastore.createQuery(clazz).field("_id").equal(id); @@ -83,39 +80,16 @@ public class MongoService<Type extends Model<IdType>, IdType> implements Service return t; } - @Override public List<Type> getMultiple(final Integer skip, final Integer limit, final String sortBy, final String... fields) { return getMultiple(QueryBuilder.builder().skip(skip).limit(limit).sortBy(sortBy).fields(true, fields)); } - @Override - public List<Type> getMultiple(final Integer skip, final Integer limit, final String sortBy, final Tuple<String, Object> criteria, - final String... fields) { - return getMultiple(QueryBuilder.builder().skip(skip).limit(limit).sortBy(sortBy).fields(true, fields).criteria(criteria)); - } - - @Override public List<Type> getMultiple(final QueryBuilder builder) { final Query<Type> query = datastore.createQuery(clazz); if (builder != null) { - if (builder.getSkip() != null && builder.getSkip() > 0) - query.offset(builder.getSkip()); - if (builder.getLimit() != null && builder.getLimit() > 0) - query.limit(builder.getLimit()); - if (builder.getSortBy() != null) - query.order(builder.getSortBy()); - if (builder.getCriteria() != null) - for (final Tuple<String, Object> criteria : builder.getCriteria()) - query.field(criteria.first()).equal(criteria.second()); - if (builder.getFields() != null) { - final String[] fields = builder.getFields(); - if (builder.isInclude()) { - query.retrievedFields(true, fields); - } else { - query.retrievedFields(false, fields); - } - } else if (!builder.isAllFields() && ignoredFieldsMultiQuery.length > 0) { + builder.build(query); + if (!builder.isAllFields() && ignoredFieldsMultiQuery.length > 0) { query.retrievedFields(false, ignoredFieldsMultiQuery); } } else if (ignoredFieldsMultiQuery.length > 0) { @@ -125,12 +99,10 @@ public class MongoService<Type extends Model<IdType>, IdType> implements Service return list; } - @Override public List<Type> getAll(final String... fields) { return getMultiple(QueryBuilder.builder().fields(true, fields)); } - @Override public Type createSingle(final Type t) throws DatabaseException { if (t == null) throw new DatabaseException(new NullPointerException("entity is null")); @@ -139,7 +111,6 @@ public class MongoService<Type extends Model<IdType>, IdType> implements Service return t; } - @Override public List<Type> createMultiple(final Iterable<Type> t) throws DatabaseException { if (t == null) throw new DatabaseException(new NullPointerException("entities are null")); @@ -149,7 +120,6 @@ public class MongoService<Type extends Model<IdType>, IdType> implements Service return list; } - @Override public long deleteSingle(final IdType id) throws DatabaseException { if (id == null) throw new DatabaseException(new NullPointerException("id is null")); @@ -158,7 +128,6 @@ public class MongoService<Type extends Model<IdType>, IdType> implements Service return deleted; } - @Override public long deleteMultiple(final Iterable<IdType> ids) throws DatabaseException { if (ids == null) throw new DatabaseException(new NullPointerException("ids are null")); @@ -167,20 +136,15 @@ public class MongoService<Type extends Model<IdType>, IdType> implements Service return deleted; } - @Override public long deleteMultiple(final QueryBuilder builder) throws DatabaseException { final Query<Type> query = datastore.createQuery(clazz); - if (builder != null) { - if (builder.getCriteria() != null) - for (final Tuple<String, Object> criteria : builder.getCriteria()) - query.field(criteria.first()).equal(criteria.second()); - } + if (builder != null) + builder.build(query); final int deleted = datastore.delete(query).getN(); return deleted; } - @Override public void replaceSingle(final Type t) throws DatabaseException { if (t == null) throw new DatabaseException(new NullPointerException("entity is null")); @@ -190,7 +154,6 @@ public class MongoService<Type extends Model<IdType>, IdType> implements Service datastore.save(t); } - @Override public void replaceMultiple(final Iterable<Type> ts) throws DatabaseException { if (ts == null) throw new DatabaseException(new NullPointerException("entities are null")); @@ -198,7 +161,6 @@ public class MongoService<Type extends Model<IdType>, IdType> implements Service datastore.save(ts); } - @Override public void updateSingle(final Type t, final boolean upsert, final String... fields) throws DatabaseException { if (t == null) throw new DatabaseException(new NullPointerException("entity is null")); @@ -233,29 +195,20 @@ public class MongoService<Type extends Model<IdType>, IdType> implements Service } } - @Override public void updateSingle(final Type t, final String... fields) throws DatabaseException { updateSingle(t, false, fields); } - @Override public void drop() { datastore.getCollection(clazz).drop(); } - @Override public long count(final QueryBuilder builder) { if (builder == null) return datastore.getCount(clazz); final Query<Type> query = datastore.createQuery(clazz); - if (builder.getSkip() != null && builder.getSkip() > 0) - query.offset(builder.getSkip()); - if (builder.getLimit() != null && builder.getLimit() > 0) - query.limit(builder.getLimit()); - if (builder.getCriteria() != null) - for (final Tuple<String, Object> criteria : builder.getCriteria()) - query.field(criteria.first()).equal(criteria.second()); + builder.build(query); return datastore.getCount(query); } diff --git a/vipra-util/src/main/java/de/vipra/util/service/QueryBuilder.java b/vipra-util/src/main/java/de/vipra/util/service/QueryBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..3fe6fd54071f732474571ed5a63a829ac1837df3 --- /dev/null +++ b/vipra-util/src/main/java/de/vipra/util/service/QueryBuilder.java @@ -0,0 +1,217 @@ +package de.vipra.util.service; + +import java.util.HashSet; +import java.util.Set; + +import org.mongodb.morphia.query.Query; + +/** + * QueryBuilder instances are used to create complex queries for use with the + * getMultiple method + * + * @see {@link Service#getMultiple(QueryBuilder)} + */ +public class QueryBuilder { + + public static enum CriterionType { + EQ, + LT, + LTE, + GT, + GTE + }; + + public static class Criterion { + public final String field; + public final CriterionType type; + public final Object value; + + public Criterion(final String field, final CriterionType type, final Object value) { + this.field = field; + this.type = type; + this.value = value; + } + + public void apply(final Query<?> query) { + switch (type) { + case EQ: + query.field(field).equal(value); + break; + case LT: + query.field(field).lessThan(value); + break; + case LTE: + query.field(field).lessThanOrEq(value); + break; + case GT: + query.field(field).greaterThan(value); + break; + case GTE: + query.field(field).greaterThanOrEq(value); + break; + } + } + } + + private Integer skip; + private Integer limit; + private String sortBy; + private Set<Criterion> criteria; + private String[] fields; + private boolean include; + private boolean allFields; + + private QueryBuilder() { + this.criteria = new HashSet<>(); + } + + public static QueryBuilder builder() { + return new QueryBuilder(); + } + + /** + * Skip n entries + * + * @param skip + * entries to skip + * @return QueryBuilder instance + */ + public QueryBuilder skip(final Integer skip) { + if (skip == null || skip >= 0) + this.skip = skip; + return this; + } + + /** + * Limit return size. + * + * @param limit + * maximum return size + * @return QueryBuilder instance + */ + public QueryBuilder limit(final Integer limit) { + if (limit == null || limit >= 0) + this.limit = limit; + return this; + } + + /** + * Sort result by field + * + * @param sortBy + * field to sort by. + * @return QueryBuilder instance + */ + public QueryBuilder sortBy(final String sortBy) { + if (sortBy == null || !sortBy.isEmpty()) + this.sortBy = sortBy; + return this; + } + + public QueryBuilder eq(final String field, final Object value) { + if (field != null && !field.isEmpty() && value != null) + criteria.add(new Criterion(field, CriterionType.EQ, value)); + return this; + } + + public QueryBuilder lt(final String field, final Object value) { + if (field != null && !field.isEmpty() && value != null) + criteria.add(new Criterion(field, CriterionType.LT, value)); + return this; + } + + public QueryBuilder lte(final String field, final Object value) { + if (field != null && !field.isEmpty() && value != null) + criteria.add(new Criterion(field, CriterionType.LTE, value)); + return this; + } + + public QueryBuilder gt(final String field, final Object value) { + if (field != null && !field.isEmpty() && value != null) + criteria.add(new Criterion(field, CriterionType.GT, value)); + return this; + } + + public QueryBuilder gte(final String field, final Object value) { + if (field != null && !field.isEmpty() && value != null) + criteria.add(new Criterion(field, CriterionType.GTE, value)); + return this; + } + + /** + * Fields to return. Set include to false to exclude. Cannot be applied + * multiple times, previous calls will be overwritten by later calls. + * + * @param include + * true to include, false to exclude + * @param fields + * fields to in/exclude + * @return QueryBuilder instance + */ + public QueryBuilder fields(final boolean include, final String... fields) { + if (fields != null) { + this.include = include; + for (final String field : fields) { + if (field.equalsIgnoreCase("_all")) { + this.allFields = true; + this.fields = null; + return this; + } + } + this.fields = fields; + } + return this; + } + + public Integer getSkip() { + return skip; + } + + public Integer getLimit() { + return limit; + } + + public String getSortBy() { + if (sortBy == null) + return null; + return sortBy.startsWith("+") ? sortBy.substring(1) : sortBy; + } + + public Set<Criterion> getCriteria() { + return criteria; + } + + public boolean isInclude() { + return include; + } + + public String[] getFields() { + return fields; + } + + public boolean isAllFields() { + return allFields; + } + + public Query<?> build(final Query<?> query) { + if (getSkip() != null && getSkip() > 0) + query.offset(getSkip()); + if (getLimit() != null && getLimit() > 0) + query.limit(getLimit()); + if (getSortBy() != null) + query.order(getSortBy()); + if (getCriteria() != null) + for (final Criterion criterion : getCriteria()) + criterion.apply(query); + if (getFields() != null) { + final String[] fields = getFields(); + if (isInclude()) { + query.retrievedFields(true, fields); + } else { + query.retrievedFields(false, fields); + } + } + return query; + } + +} \ No newline at end of file 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 deleted file mode 100644 index d198189b168668a7059308d32f62cac0f45342d3..0000000000000000000000000000000000000000 --- a/vipra-util/src/main/java/de/vipra/util/service/Service.java +++ /dev/null @@ -1,319 +0,0 @@ -package de.vipra.util.service; - -import java.util.ArrayList; -import java.util.List; - -import de.vipra.util.Tuple; -import de.vipra.util.model.Model; - -/** - * Generic service interface, implemented by various database services to - * support multiple database engines. - * - * @param <Type> - * Model type that is returned by service functions - * @param <IdType> - * Id type that is used to identify models in the database - * @param <E> - * Type of exception - */ -public interface Service<Type extends Model<IdType>, IdType, E extends Exception> { - - /** - * @see {@link Service#getSingle(Object, QueryBuilder)} - */ - Type getSingle(IdType id, String... fields) throws E; - - /** - * Returns a single entity from the database or null - * - * @param id - * id of the entity - * @param builder - * query builder - * @return retrieved entity or null - * @throws E - */ - Type getSingle(IdType id, QueryBuilder builder) throws E; - - /** - * @see {@link Service#getMultiple(QueryBuilder)} - */ - List<Type> getMultiple(Integer skip, Integer limit, String sortBy, String... fields) throws E; - - /** - * @see {@link Service#getMultiple(QueryBuilder)} - */ - List<Type> getMultiple(Integer skip, Integer limit, String sortBy, Tuple<String, Object> criteria, String... fields) throws E; - - /** - * Returns multiple entities from the database. - * - * @param builder - * query builder - * @return found entities - * @throws E - * @see {@link QueryBuilder} - */ - List<Type> getMultiple(QueryBuilder builder) throws E; - - /** - * Return all entities. - * - * @param fields - * fields to be returned - * @return all entities - * @throws E - */ - List<Type> getAll(String... fields) throws E; - - /** - * Create a single entity in the database - * - * @param t - * Entity to be created - * @return Created entity, with inserted id if supported - * @throws E - */ - Type createSingle(Type t) throws E; - - /** - * Create multiple entities in the database - * - * @param t - * Entities to be created - * @return Created entities, with inserted ids if supported - * @throws E - */ - List<Type> createMultiple(Iterable<Type> t) throws E; - - /** - * Deletes a single entity from the database - * - * @param id - * id of entity to be deleted - * @return number of deleted entities - * @throws E - */ - long deleteSingle(IdType id) throws E; - - /** - * Deletes multiple entries from the database - * - * @param ids - * Entities to be deleted - * @return number of deleted entries - * @throws E - */ - long deleteMultiple(Iterable<IdType> ids) throws E; - - long deleteMultiple(QueryBuilder builder) throws E; - - /** - * Replaces a single entity in the database - * - * @param t - * Entity to be updated - * @throws E - */ - void replaceSingle(Type t) throws E; - - /** - * Replaces multiple entities in the database - * - * @param ts - * Entities to be updated - * @throws E - */ - void replaceMultiple(Iterable<Type> ts) throws E; - - /** - * Updates a single entity in the database - * - * @param t - * Entity to be updated - * @param upsert - * true to insert if not exists - * @param fields - * Fields to be updated - * @throws E - */ - void updateSingle(Type t, boolean upsert, String... fields) throws E; - - /** - * Updates a single entity in the database - * - * @param t - * Entity to be updated - * @param fields - * Fields to be updated - * @throws E - */ - void updateSingle(Type t, String... fields) throws E; - - /** - * Drop all entities from the database - * - * @throws E - */ - void drop() throws E; - - /** - * Count entities in the database - * - * @return number of entities in the database - * @throws E - */ - long count(QueryBuilder builder) throws E; - - /** - * QueryBuilder instances are used to create complex queries for use with - * the getMultiple method - * - * @see {@link Service#getMultiple(QueryBuilder)} - */ - public static class QueryBuilder { - - private Integer skip; - private Integer limit; - private String sortBy; - private List<Tuple<String, Object>> criteria; - private String[] fields; - private boolean include; - private boolean allFields; - - private QueryBuilder() {} - - public static QueryBuilder builder() { - return new QueryBuilder(); - } - - /** - * Skip n entries - * - * @param skip - * entries to skip - * @return QueryBuilder instance - */ - public QueryBuilder skip(final Integer skip) { - if (skip == null || skip >= 0) - this.skip = skip; - return this; - } - - /** - * Limit return size. - * - * @param limit - * maximum return size - * @return QueryBuilder instance - */ - public QueryBuilder limit(final Integer limit) { - if (limit == null || limit >= 0) - this.limit = limit; - return this; - } - - /** - * Sort result by field - * - * @param sortBy - * field to sort by. - * @return QueryBuilder instance - */ - public QueryBuilder sortBy(final String sortBy) { - if (sortBy == null || !sortBy.isEmpty()) - this.sortBy = sortBy; - return this; - } - - /** - * Criteria used for filtering by field - * - * @param field - * field to compare - * @param value - * value to compare to - * @return QueryBuilder instance - */ - public QueryBuilder criteria(final String field, final Object value) { - if (field != null && value != null && !field.isEmpty()) - criteria(Tuple.pair(field, value)); - return this; - } - - /** - * Criteria used for filtering by field - * - * @param pair - * field value pair to compare - * @return QueryBuilder instance - */ - public QueryBuilder criteria(final Tuple<String, Object> pair) { - if (pair != null) { - if (criteria == null) { - criteria = new ArrayList<>(); - } - criteria.add(pair); - } - return this; - } - - /** - * Fields to return. Set include to false to exclude. Cannot be applied - * multiple times, previous calls will be overwritten by later calls. - * - * @param include - * true to include, false to exclude - * @param fields - * fields to in/exclude - * @return QueryBuilder instance - */ - public QueryBuilder fields(final boolean include, final String... fields) { - if (fields != null) { - this.include = include; - for (final String field : fields) { - if (field.equalsIgnoreCase("_all")) { - this.allFields = true; - this.fields = null; - return this; - } - } - this.fields = fields; - } - return this; - } - - public Integer getSkip() { - return skip; - } - - public Integer getLimit() { - return limit; - } - - public String getSortBy() { - if (sortBy == null) - return null; - return sortBy.startsWith("+") ? sortBy.substring(1) : sortBy; - } - - public List<Tuple<String, Object>> getCriteria() { - return criteria; - } - - public boolean isInclude() { - return include; - } - - public String[] getFields() { - return fields; - } - - public boolean isAllFields() { - return allFields; - } - - } - -}