From e4ecaceb31e2e89d06c48f24e4c59a0c702b8ffe Mon Sep 17 00:00:00 2001
From: Eike Cochu <eike@cochu.com>
Date: Sun, 20 Dec 2015 18:14:17 +0100
Subject: [PATCH] generalized util services fixed import option exception
 structure

---
 .../main/java/de/vipra/cmd/CmdOptions.java    |  9 +--
 .../src/main/java/de/vipra/cmd/Main.java      | 30 ++++---
 .../java/de/vipra/cmd/option/Command.java     |  9 +++
 .../{ImportOption.java => ImportCommand.java} | 79 ++++++++-----------
 .../vipra/rest/resource/ArticleResource.java  | 45 +++++++----
 .../de/vipra/rest/service/ArticleService.java |  5 +-
 .../de/vipra/util/ex/DatabaseException.java   | 15 ++++
 .../de/vipra/util/ex/FilebaseException.java   | 15 ++++
 .../vipra/util/service/DatabaseService.java   | 30 +++++--
 .../vipra/util/service/FilebaseService.java   | 47 +++++++++--
 .../java/de/vipra/util/service/Service.java   | 15 ++++
 11 files changed, 204 insertions(+), 95 deletions(-)
 create mode 100644 vipra-cmd/src/main/java/de/vipra/cmd/option/Command.java
 rename vipra-cmd/src/main/java/de/vipra/cmd/option/{ImportOption.java => ImportCommand.java} (62%)
 create mode 100644 vipra-util/src/main/java/de/vipra/util/ex/DatabaseException.java
 create mode 100644 vipra-util/src/main/java/de/vipra/util/ex/FilebaseException.java
 create mode 100644 vipra-util/src/main/java/de/vipra/util/service/Service.java

diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/CmdOptions.java b/vipra-cmd/src/main/java/de/vipra/cmd/CmdOptions.java
index cf60b706..6cfd7f0a 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/CmdOptions.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/CmdOptions.java
@@ -2,7 +2,6 @@ package de.vipra.cmd;
 
 import org.apache.commons.cli.HelpFormatter;
 import org.apache.commons.cli.Option;
-import org.apache.commons.cli.OptionGroup;
 import org.apache.commons.cli.Options;
 
 public class CmdOptions extends Options {
@@ -10,6 +9,7 @@ public class CmdOptions extends Options {
 	private static final long serialVersionUID = 1L;
 
 	public static final String OPT_HELP = "h";
+	public static final String OPT_HELP_LONG = "help";
 	public static final String OPT_IMPORT = "i";
 	public static final String OPT_SHELL = "x";
 
@@ -17,14 +17,9 @@ public class CmdOptions extends Options {
 		addOption(Option.builder(OPT_HELP).longOpt("help").desc("print this message").build());
 		addOption(Option.builder(OPT_SHELL).longOpt("shell").hasArg(true).argName("name")
 				.desc("run from a shell script").build());
-
-		OptionGroup opts = new OptionGroup();
-		opts.setRequired(true);
-
-		opts.addOption(Option.builder(OPT_IMPORT).longOpt("import").hasArgs().argName("files/dirs...")
+		addOption(Option.builder(OPT_IMPORT).longOpt("import").hasArgs().argName("files/dirs...")
 				.desc("import articles into the database").build());
 
-		addOptionGroup(opts);
 	}
 
 	public void printHelp(String cmd) {
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 152984f9..9ffcc8bf 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/Main.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/Main.java
@@ -4,8 +4,6 @@ import static de.vipra.cmd.CmdOptions.OPT_HELP;
 import static de.vipra.cmd.CmdOptions.OPT_IMPORT;
 import static de.vipra.cmd.CmdOptions.OPT_SHELL;
 
-import java.io.IOException;
-
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.CommandLineParser;
 import org.apache.commons.cli.DefaultParser;
@@ -13,19 +11,27 @@ import org.apache.commons.cli.ParseException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import de.vipra.cmd.option.ImportOption;
-import de.vipra.util.ConfigException;
+import de.vipra.cmd.option.Command;
+import de.vipra.cmd.option.ImportCommand;
 
 public class Main {
 
 	public static final Logger log = LoggerFactory.getLogger(Main.class);
 
-	public static void main(String[] args) throws IOException, ConfigException {
+	public static void main(String[] args) {
 		CommandLineParser parser = new DefaultParser();
 		CmdOptions options = new CmdOptions();
 		String cmd = "vipra-cmd.jar";
+		CommandLine cline;
+		try {
+			cline = parser.parse(options, args);
+		} catch (ParseException e) {
+			log.error(e.getMessage());
+			options.printHelp(cmd);
+			return;
+		}
+
 		try {
-			CommandLine cline = parser.parse(options, args);
 			if (cline.hasOption(OPT_SHELL)) {
 				cmd = cline.getOptionValue(OPT_SHELL);
 				if (cmd == null) {
@@ -33,18 +39,20 @@ public class Main {
 				}
 			}
 
+			Command c = null;
+
 			if (cline.hasOption(OPT_HELP)) {
 				options.printHelp(cmd);
 			} else if (cline.hasOption(OPT_IMPORT)) {
 				String[] paths = cline.getOptionValues(OPT_IMPORT);
-				ImportOption opt = new ImportOption(paths);
-				opt.doImport();
+				c = new ImportCommand(paths);
+			}
+
+			if (c != null) {
+				c.run();
 			} else {
 				options.printHelp(cmd);
 			}
-		} catch (ParseException e) {
-			log.error(e.getMessage());
-			options.printHelp(cmd);
 		} catch (ExecutionException e) {
 			log.error(e.getMessage());
 		}
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/Command.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/Command.java
new file mode 100644
index 00000000..5ad51101
--- /dev/null
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/Command.java
@@ -0,0 +1,9 @@
+package de.vipra.cmd.option;
+
+import de.vipra.cmd.ExecutionException;
+
+public interface Command {
+
+	public void run() throws ExecutionException;
+
+}
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportOption.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java
similarity index 62%
rename from vipra-cmd/src/main/java/de/vipra/cmd/option/ImportOption.java
rename to vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java
index c7eb0546..807335a3 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportOption.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java
@@ -7,17 +7,12 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
-import org.bson.Document;
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.json.simple.parser.JSONParser;
-import org.json.simple.parser.ParseException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.mongodb.client.MongoCollection;
-import com.mongodb.client.MongoDatabase;
-
 import de.vipra.cmd.ExecutionException;
 import de.vipra.cmd.model.Article;
 import de.vipra.util.Config;
@@ -25,18 +20,23 @@ import de.vipra.util.ConfigException;
 import de.vipra.util.Constants;
 import de.vipra.util.Mongo;
 import de.vipra.util.StringUtils;
+import de.vipra.util.ex.DatabaseException;
+import de.vipra.util.ex.FilebaseException;
+import de.vipra.util.service.DatabaseService;
+import de.vipra.util.service.FilebaseService;
 
-public class ImportOption {
+public class ImportCommand implements Command {
 
-	public static final Logger log = LoggerFactory.getLogger(ImportOption.class);
+	public static final Logger log = LoggerFactory.getLogger(ImportCommand.class);
 	public static final Logger out = LoggerFactory.getLogger("shellout");
 
 	private ArrayList<File> files = new ArrayList<>();
 	private JSONParser parser = new JSONParser();
 	private Config config;
-	private MongoCollection<Document> collection;
+	private DatabaseService<Article> dbArticles;
+	private FilebaseService<Article> fbArticles;
 
-	public ImportOption(String[] paths) throws ExecutionException {
+	public ImportCommand(String[] paths) throws ExecutionException {
 		addPaths(paths);
 	}
 
@@ -61,42 +61,21 @@ public class ImportOption {
 			files.add(file);
 		} else if (file.isDirectory()) {
 			File[] files = file.listFiles(new FilenameFilter() {
-
 				@Override
 				public boolean accept(File dir, String name) {
 					return dir.isFile();
 				}
-
 			});
 
 			addPaths(files);
 		}
 	}
 
-	private void importFile(File file) throws ExecutionException {
-		Object data;
-		try {
-			data = parser.parse(new FileReader(file));
-		} catch (IOException e) {
-			throw new ExecutionException("file not found: " + file.getAbsolutePath());
-		} catch (ParseException e) {
-			throw new ExecutionException("could not parse json in file: " + file.getAbsolutePath());
-		}
+	private void importFile(File file) throws Exception {
+		Object data = parser.parse(new FileReader(file));
 
 		try {
-			JSONArray array = (JSONArray) data;
-			for (Object object : array) {
-				List<Exception> errors = new ArrayList<>();
-				try {
-					importArticle((JSONObject) object);
-				} catch (ExecutionException e) {
-					errors.add(e);
-				}
-				if (errors.size() > 0) {
-					throw new ExecutionException(errors);
-				}
-			}
-			return;
+			importArticles((JSONArray) data);
 		} catch (ClassCastException e) {
 			try {
 				importArticle((JSONObject) data);
@@ -106,8 +85,21 @@ public class ImportOption {
 		}
 	}
 
-	private void importArticle(JSONObject obj) throws ExecutionException {
-		// 2. add article to file database
+	private void importArticles(JSONArray array) throws ExecutionException {
+		List<Exception> errors = new ArrayList<>();
+		for (Object object : array) {
+			try {
+				importArticle((JSONObject) object);
+			} catch (Exception e) {
+				errors.add(e);
+			}
+		}
+		if (errors.size() > 0) {
+			throw new ExecutionException(errors);
+		}
+	}
+
+	private void importArticle(JSONObject obj) throws FilebaseException, DatabaseException {
 		// 4. topic modeling
 		// 3. index article via elasticsearch, include topics
 		out.info("importing \"" + StringUtils.ellipsize(obj.get("title").toString(), 80) + "\"");
@@ -115,21 +107,20 @@ public class ImportOption {
 		article.fromJSON(obj);
 
 		// add article to mongodb
-		Document document = article.toDocument();
-		try {
-			collection.insertOne(document);
-		} catch (Exception e) {
-			throw new ExecutionException("could not create database entry: " + e.getMessage());
-		}
+		article = dbArticles.createSingle(article);
 
+		// add article to filebase
+		article = fbArticles.createSingle(article);
 	}
 
-	public void doImport() throws ExecutionException {
+	@Override
+	public void run() throws ExecutionException {
 		try {
 			config = new Config();
+			File dataDir = null;
 			Mongo mongo = Mongo.getInstance(config);
-			MongoDatabase db = mongo.getDatabase();
-			collection = db.getCollection(Constants.Collection.ARTICLES.name);
+			dbArticles = new DatabaseService<Article>(mongo, Constants.Collection.ARTICLES, Article.class);
+			fbArticles = new FilebaseService<Article>(dataDir, Article.class);
 		} catch (IOException | ConfigException e) {
 			throw new ExecutionException(e);
 		}
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 c346a083..526d14d3 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
@@ -27,6 +27,7 @@ import de.vipra.rest.service.ArticleService;
 import de.vipra.util.Config;
 import de.vipra.util.ConfigException;
 import de.vipra.util.Mongo;
+import de.vipra.util.ex.DatabaseException;
 
 @Path("articles")
 public class ArticleResource {
@@ -79,9 +80,16 @@ public class ArticleResource {
 	@Consumes(APIMediaType.APPLICATION_JSONAPI)
 	@Produces(APIMediaType.APPLICATION_JSONAPI)
 	public Response createArticle(Article article) {
-		article = service.createArticle(uri.getAbsolutePath(), article);
-		ResponseWrapper<Article> res = new ResponseWrapper<>(article);
-		return Response.created(article.uri(uri.getAbsolutePath())).entity(res).build();
+		ResponseWrapper<Article> res;
+		try {
+			article = service.createArticle(uri.getAbsolutePath(), article);
+			res = new ResponseWrapper<>(article);
+			return Response.created(article.uri(uri.getAbsolutePath())).entity(res).build();
+		} catch (DatabaseException e) {
+			res = new ResponseWrapper<>(new APIError(Response.Status.INTERNAL_SERVER_ERROR, "item could not be created",
+					"item could not be created due to an internal server error"));
+			return Response.serverError().entity(res).build();
+		}
 	}
 
 	@DELETE
@@ -107,20 +115,27 @@ public class ArticleResource {
 	@Produces(APIMediaType.APPLICATION_JSONAPI)
 	@Path("{id}")
 	public Response updateArticle(@PathParam("id") String id, Article article) {
-		long updated = service.updateArticle(uri.getAbsolutePath(), article);
-		int upd = updated > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) updated;
 		ResponseWrapper<Article> res = new ResponseWrapper<>();
-		switch (upd) {
-		case 0:
-			res.addError(new APIError(Response.Status.NOT_FOUND, "Article not found",
-					String.format(Messages.NOT_FOUND, "article", id)));
-			return Response.status(Response.Status.NOT_FOUND).entity(res).build();
-		case 1:
-			res.setData(article);
-			return Response.ok().entity(res).build();
-		default:
-			return Response.serverError().build();
+		try {
+			long updated = service.updateArticle(uri.getAbsolutePath(), article);
+			int updatedInt = updated > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) updated;
+			switch (updatedInt) {
+			case 0:
+				res.addError(new APIError(Response.Status.NOT_FOUND, "Article not found",
+						String.format(Messages.NOT_FOUND, "article", id)));
+				return Response.status(Response.Status.NOT_FOUND).entity(res).build();
+			case 1:
+				res.setData(article);
+				return Response.ok().entity(res).build();
+			default:
+				return Response.serverError().build();
+			}
+		} catch (DatabaseException e) {
+			res = new ResponseWrapper<>(new APIError(Response.Status.INTERNAL_SERVER_ERROR, "item could not be updated",
+					"item could not be updated due to an internal server error"));
+			return Response.serverError().entity(res).build();
 		}
+
 	}
 
 }
diff --git a/vipra-rest/src/main/java/de/vipra/rest/service/ArticleService.java b/vipra-rest/src/main/java/de/vipra/rest/service/ArticleService.java
index 4f7c1e93..9f432a2a 100644
--- a/vipra-rest/src/main/java/de/vipra/rest/service/ArticleService.java
+++ b/vipra-rest/src/main/java/de/vipra/rest/service/ArticleService.java
@@ -6,6 +6,7 @@ import java.util.ArrayList;
 import de.vipra.rest.model.Article;
 import de.vipra.util.Constants;
 import de.vipra.util.Mongo;
+import de.vipra.util.ex.DatabaseException;
 import de.vipra.util.service.DatabaseService;
 
 public class ArticleService extends DatabaseService<Article> {
@@ -31,7 +32,7 @@ public class ArticleService extends DatabaseService<Article> {
 		return articles;
 	}
 
-	public Article createArticle(URI base, Article article) {
+	public Article createArticle(URI base, Article article) throws DatabaseException {
 		article = super.createSingle(article);
 		if (article != null) {
 			article.setBase(base);
@@ -43,7 +44,7 @@ public class ArticleService extends DatabaseService<Article> {
 		return super.deleteSingle(id);
 	}
 
-	public long updateArticle(URI base, Article article) {
+	public long updateArticle(URI base, Article article) throws DatabaseException {
 		long updated = super.updateSingle(article);
 		article.setBase(base);
 		return updated;
diff --git a/vipra-util/src/main/java/de/vipra/util/ex/DatabaseException.java b/vipra-util/src/main/java/de/vipra/util/ex/DatabaseException.java
new file mode 100644
index 00000000..6782255b
--- /dev/null
+++ b/vipra-util/src/main/java/de/vipra/util/ex/DatabaseException.java
@@ -0,0 +1,15 @@
+package de.vipra.util.ex;
+
+public class DatabaseException extends Exception {
+
+	private static final long serialVersionUID = 1L;
+
+	public DatabaseException(String msg) {
+		super(msg);
+	}
+
+	public DatabaseException(Exception e) {
+		super(e);
+	}
+
+}
diff --git a/vipra-util/src/main/java/de/vipra/util/ex/FilebaseException.java b/vipra-util/src/main/java/de/vipra/util/ex/FilebaseException.java
new file mode 100644
index 00000000..99dd450e
--- /dev/null
+++ b/vipra-util/src/main/java/de/vipra/util/ex/FilebaseException.java
@@ -0,0 +1,15 @@
+package de.vipra.util.ex;
+
+public class FilebaseException extends Exception {
+
+	private static final long serialVersionUID = 1L;
+
+	public FilebaseException(String msg) {
+		super(msg);
+	}
+
+	public FilebaseException(Exception e) {
+		super(e);
+	}
+
+}
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 4729cf84..9b774258 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
@@ -16,9 +16,10 @@ import com.mongodb.client.result.UpdateResult;
 
 import de.vipra.util.Constants;
 import de.vipra.util.Mongo;
+import de.vipra.util.ex.DatabaseException;
 import de.vipra.util.model.Model;
 
-public class DatabaseService<T extends Model> {
+public class DatabaseService<T extends Model> implements Service<T, DatabaseException> {
 
 	private static final Logger log = LoggerFactory.getLogger(DatabaseService.class);
 
@@ -41,7 +42,8 @@ public class DatabaseService<T extends Model> {
 		}
 	}
 
-	protected T getSingle(String id) {
+	@Override
+	public T getSingle(String id) {
 		ArrayList<Document> result = collection.find(Filters.eq("_id", objectId(id))).into(new ArrayList<Document>());
 		if (result.size() == 1) {
 			return newT(result.get(0));
@@ -50,7 +52,7 @@ public class DatabaseService<T extends Model> {
 		}
 	}
 
-	protected ArrayList<T> getMultiple(int skip, int limit, String sortBy) {
+	public ArrayList<T> getMultiple(int skip, int limit, String sortBy) {
 		ArrayList<Document> documents = collection.find().skip(skip).limit(limit).sort(getSorts(sortBy))
 				.into(new ArrayList<Document>());
 		ArrayList<T> items = new ArrayList<>(documents.size());
@@ -62,22 +64,34 @@ public class DatabaseService<T extends Model> {
 		return items;
 	}
 
-	protected T createSingle(T t) {
+	@Override
+	public T createSingle(T t) throws DatabaseException {
 		Document document = new Document(t.toDocument());
-		collection.insertOne(document);
+		try {
+			collection.insertOne(document);
+		} catch (Exception e) {
+			throw new DatabaseException(e);
+		}
 		t.fromDocument(document);
 		return t;
 	}
 
-	protected long deleteSingle(String id) {
+	@Override
+	public long deleteSingle(String id) {
 		DeleteResult result = collection.deleteOne(Filters.eq("_id", objectId(id)));
 		return result.getDeletedCount();
 	}
 
-	protected long updateSingle(T t) {
+	@Override
+	public long updateSingle(T t) throws DatabaseException {
 		Document docOld = new Document("_id", objectId(t.getId()));
 		Document docNew = t.toDocument();
-		UpdateResult result = collection.replaceOne(docOld, docNew);
+		UpdateResult result;
+		try {
+			result = collection.replaceOne(docOld, docNew);
+		} catch (Exception e) {
+			throw new DatabaseException(e);
+		}
 		t.fromDocument(docNew);
 		return result.getModifiedCount();
 	}
diff --git a/vipra-util/src/main/java/de/vipra/util/service/FilebaseService.java b/vipra-util/src/main/java/de/vipra/util/service/FilebaseService.java
index 3dfa8c74..e369e216 100644
--- a/vipra-util/src/main/java/de/vipra/util/service/FilebaseService.java
+++ b/vipra-util/src/main/java/de/vipra/util/service/FilebaseService.java
@@ -6,9 +6,10 @@ import java.io.IOException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import de.vipra.util.ex.FilebaseException;
 import de.vipra.util.model.Model;
 
-public class FilebaseService<T extends Model> {
+public class FilebaseService<T extends Model> implements Service<T, FilebaseException> {
 
 	public static final Logger log = LoggerFactory.getLogger(FilebaseService.class);
 
@@ -32,27 +33,57 @@ public class FilebaseService<T extends Model> {
 		}
 	}
 
-	protected File getFile(String id) {
+	public File getFile(String id) {
 		return new File(directory, id);
 	}
 
-	protected T getSingle(String id) {
+	@Override
+	public T getSingle(String id) {
 		File file = getFile(id);
 		return newT(file);
 	}
 
-	protected long deleteSingle(String id) {
+	@Override
+	public T createSingle(T t) throws FilebaseException {
+		if (t.getId() != null) {
+			File file = getFile(t.getId());
+			if (file.exists()) {
+				if (!file.delete()) {
+					log.error("could not delete file for recreation: " + file.getAbsolutePath());
+				}
+			}
+			try {
+				t.writeToFile(file);
+			} catch (IOException e) {
+				throw new FilebaseException(e);
+			}
+		}
+		return t;
+	}
+
+	@Override
+	public long deleteSingle(String id) throws FilebaseException {
 		File file = getFile(id);
-		if (file.exists() && file.delete()) {
-			return 1;
+		if (file.exists()) {
+			if (file.delete()) {
+				return 1;
+			} else {
+				throw new FilebaseException("could not delete file: " + file.getAbsolutePath());
+			}
 		}
 		return 0;
 	}
 
-	protected long updateSingle(T t) throws IOException {
+	@Override
+	public long updateSingle(T t) throws FilebaseException {
 		File file = getFile(t.getId());
 		if (file.exists()) {
-			t.writeToFile(file);
+			try {
+				t.writeToFile(file);
+				return 1;
+			} catch (Exception e) {
+				throw new FilebaseException(e);
+			}
 		}
 		return 0;
 	}
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
new file mode 100644
index 00000000..824a6a97
--- /dev/null
+++ b/vipra-util/src/main/java/de/vipra/util/service/Service.java
@@ -0,0 +1,15 @@
+package de.vipra.util.service;
+
+import de.vipra.util.model.Model;
+
+public interface Service<T extends Model, E extends Exception> {
+
+	T getSingle(String id) throws E;
+
+	T createSingle(T t) throws E;
+
+	long deleteSingle(String id) throws E;
+
+	long updateSingle(T t) throws E;
+
+}
-- 
GitLab