All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sdmlib.modelcouch.ModelCouch Maven / Gradle / Ivy

Go to download

SDMLib is a light weight modeling library. SDMLib intentionally comes without any tool or editor.

There is a newer version: 2.3.2341
Show newest version
/*
   Copyright (c) 2016 deeptought

   Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
   and associated documentation files (the "Software"), to deal in the Software without restriction, 
   including without limitation the rights to use, copy, modify, merge, publish, distribute, 
   sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
   furnished to do so, subject to the following conditions: 

   The above copyright notice and this permission notice shall be included in all copies or 
   substantial portions of the Software. 

   The Software shall be used for Good, not Evil. 

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 
   BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
   DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
 */

package org.sdmlib.modelcouch;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.sdmlib.StrUtil;
import org.sdmlib.replication.ChangeEvent;
import org.sdmlib.serialization.PropertyChangeInterface;

import de.uniks.networkparser.EntityUtil;
import de.uniks.networkparser.IdMap;
import de.uniks.networkparser.interfaces.SendableEntity;
import de.uniks.networkparser.interfaces.SendableEntityCreator;
import de.uniks.networkparser.interfaces.UpdateListener;
import de.uniks.networkparser.json.JsonArray;
import de.uniks.networkparser.json.JsonObject;
import de.uniks.networkparser.logic.SimpleMapEvent;
import javafx.concurrent.Task;

import org.sdmlib.modelcouch.ModelDBListener;
import org.sdmlib.modelcouch.authentication.Authenticator;
import org.sdmlib.modelcouch.authentication.BasicAuthenticator;

/**
 * 
 * @see 
 *      ModelCouchModel.java
 * @see 
 *      ModelCouchModel.java
 * @see ModelCouchModel.java
 */
public class ModelCouch implements SendableEntity, PropertyChangeInterface, UpdateListener {
	public enum ApplicationType {
		StandAlone, JavaFX
	};

	private ApplicationType applicationType;

	private final int RESPONSE_CODE_DB_MISSING = 404;

	private IdMap idMap;
	private String databaseName;
	private String userName = "couchdb";

	private ExecutorService executor = Executors.newFixedThreadPool(2);
	private long lastChangeId = 0;

	private ConcurrentLinkedQueue> queue = new ConcurrentLinkedQueue<>();

	private CouchDBAdapter couchDBAdapter;
	
	public CouchDBAdapter getCouchDBAdapter() {
		return couchDBAdapter;
	}
	
	public void setCouchDBAdapter(CouchDBAdapter couchAdapter) {
		this.couchDBAdapter = couchAdapter;
	}

	public ModelCouch(CouchDBAdapter couchAdapter) {
		this.couchDBAdapter = couchAdapter;
	}

	public ModelCouch registerAtIdMap() {
		idMap.with(this);
		return this;
	}

	public URL getURL() {
		String urlString = "http://" + hostName + ":" + port + "/" + databaseName + "/";
		try {
			return new URL(urlString);
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	// try to open connection to an existing database
	// create new if database was not existing
	public ModelCouch open(String databaseName) {
		this.databaseName = databaseName;

		String urlString = "http://" + hostName + ":" + port + "/" + databaseName + "/";
		try {
			URL urlObj = new URL(urlString);
			HttpURLConnection con = (HttpURLConnection) urlObj.openConnection();

			// is database existing?
			con.setRequestMethod("GET");
			con.setDoInput(true);
			con.setDoOutput(true);
			con.setRequestProperty("Content-Type", "application/json");

			int responseCode = con.getResponseCode();

			con.disconnect();

			if (responseCode == RESPONSE_CODE_DB_MISSING) {
				con.disconnect();
				// create
				this.couchDBAdapter.createDB(databaseName);
			}

			mdbListener = createModelDBListener();

			mdbListener.loadOldChanges();

			if (isContinuous()) {
				start();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		return this;
	}

	private Future started = null;

	public Future start() {
		if (!isContinuous() || isContinuous() && (started == null || started != null && started.isDone())) {
			mdbListener.setContinuous(isContinuous());
			started = executor.submit(mdbListener, true);
		}
		return started;
	}

	public void start(boolean wait, long timeout) {
		Future start = start();
		if (start == null)
			return;
		if (wait) {
			try {
				start.get(timeout, TimeUnit.MILLISECONDS);
			} catch (InterruptedException | ExecutionException | TimeoutException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	EntityUtil entityUtil = new EntityUtil();

	public int send(ChangeEvent change) {
		FutureTask task = new FutureTask(new Callable() {
			@Override
			public Integer call() throws Exception {
				int responsecode = -1;

				JsonObject jsonObject = change.toJson();

				String url = "http://" + hostName + ":" + port + "/" + databaseName + "/";
				try {
					URL obj = new URL(url);
					HttpURLConnection con = (HttpURLConnection) obj.openConnection();
					String urlParameters = jsonObject.toString();

					urlParameters = entityUtil.encode(urlParameters);

					con.setRequestMethod("POST");
					con.setDoOutput(true);
					con.setDoInput(true);
					couchDBAdapter.authenticate(con);
					con.addRequestProperty("Content-Type", "application/json");
					DataOutputStream wr = new DataOutputStream(con.getOutputStream());
					wr.writeBytes(urlParameters);
					wr.flush();
					wr.close();

					responsecode = con.getResponseCode();
					con.disconnect();
				} catch (Exception e) {
					e.printStackTrace();
				}

				return responsecode;
			}

		});
		if (isContinuous()) {
			try {
				executor.submit(task);
				return task.get();
			} catch (InterruptedException | ExecutionException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else {
			executor.submit(task);
		}
		return 0;
	}

	public int delete(JsonObject change) {
		/**
		 * url for delete should look like
		 * "http://localhost:5989/dss/7264fb7568709355423ca63ee1c97b94/001?rev=1-6c8068c8e999096447efef68d4e76c53"
		 */
		FutureTask task = new FutureTask(new Callable() {
			@Override
			public Integer call() throws Exception {
				int responsecode = -1;

				JsonObject jsonObject = change;
				String objId = jsonObject.getValue("id").toString();
				JsonArray changeRev = (JsonArray) jsonObject.getValue("changes");
				JsonObject rev = ((JsonObject) changeRev.get(0));

				String url = "http://" + hostName + ":" + port + "/" + databaseName + "/" + objId + "?"
						+ rev.getKeyByIndex(0) + "=" + rev.getValueByIndex(0);
				try {
					URL obj = new URL(url);
					HttpURLConnection con = (HttpURLConnection) obj.openConnection();

					con.setRequestMethod("DELETE");
					con.addRequestProperty("Content-Type", "application/json");
					couchDBAdapter.authenticate(con);

					responsecode = con.getResponseCode();
					con.disconnect();
				} catch (Exception e) {
					e.printStackTrace();
				}

				return responsecode;
			}
		});
		if (isContinuous()) {
			try {
				executor.submit(task);
				return task.get();
			} catch (InterruptedException | ExecutionException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else {
			executor.submit(task);
		}
		return 0;
	}

	public void close() {
		executor.shutdownNow();
		this.removeYou();
	}

	public String getDatabaseName() {
		return databaseName;
	}

	// ==============================================================================
	public long getNewHistoryIdNumber() {
		long result = System.currentTimeMillis();

		if (result <= lastChangeId) {
			result = lastChangeId + 1;
		}

		lastChangeId = result;

		return result;
	}

	@Override
	public boolean update(Object event) {
		if (mdbListener.isApplyingChangeMsg()) {
			// ignore
			return true;
		}
		SimpleMapEvent simpleEvent = (SimpleMapEvent) event;
		JsonObject jsonObject = (JsonObject) simpleEvent.getEntity();

		String opCode = IdMap.UPDATE;

		Object attributes = jsonObject.get(IdMap.UPDATE);

		if (attributes == null) {
			attributes = jsonObject.get(IdMap.REMOVE);
			opCode = IdMap.REMOVE;

			if (attributes == null) {
				attributes = jsonObject.get("prop");
				opCode = IdMap.UPDATE;
			}
		}

		JsonObject valueJsonObject = null;
		JsonObject attributesJson = null;
		String prop = null;

		if (attributes != null) {
			attributesJson = (JsonObject) attributes;

			Iterator iter = attributesJson.keyIterator();
			while (iter.hasNext()) {
				prop = iter.next();

				ChangeEvent change = new ChangeEvent().withSessionId(((IdMap) idMap).getCounter().getPrefixId())
						.withObjectId(jsonObject.getString(IdMap.ID)).withChangeNo("" + getNewHistoryIdNumber())
						.withObjectType(jsonObject.getString(IdMap.CLASS)).withProperty(prop);

				Object attrValue = attributesJson.get(prop);

				if (attrValue instanceof JsonObject) {
					JsonArray valueJsonArray = new JsonArray();
					valueJsonArray.add(attrValue);
					attrValue = valueJsonArray;
				}

				if (attrValue instanceof JsonArray) {
					JsonArray valueJsonArray = (JsonArray) attrValue;

					for (Object arrayElem : valueJsonArray) {
						valueJsonObject = (JsonObject) arrayElem;

						String valueObjectId = (String) valueJsonObject.get(IdMap.ID);

						String valueObjectType = (String) valueJsonObject.get(IdMap.CLASS);

						Object valueObject = idMap.getObject(valueObjectId);

						if (valueObjectType == null) {
							// get object and ask it
							valueObjectType = valueObject.getClass().getName();
						}

						change.withValueType(valueObjectType);

						// toOne or toMany
						change.withPropertyKind(ChangeEvent.TO_ONE);

						Object targetObject = idMap.getObject(change.getObjectId());

						SendableEntityCreator creator = idMap.getCreatorClass(targetObject);

						Object value = creator.getValue(targetObject, change.getProperty());

						if (value != null && value instanceof Collection) {
							change.setPropertyKind(ChangeEvent.TO_MANY);
						}

						// newValue or oldValue?
						if (opCode.equals(IdMap.REMOVE)) {
							change.withOldValue(valueObjectId);
						} else {
							change.withNewValue(valueObjectId);
						}

						// send it
						send(change);

						// does the value have properties?
						if (valueJsonObject.get("prop") != null) {
							// call recursive
							// this.update(typ, valueJsonObject, valueObject,
							// prop, null, null);
							simpleEvent.with(valueJsonObject);
							this.update(simpleEvent);
						}
					}
				} else {
					PropertyChangeEvent evt = (PropertyChangeEvent) event;
					String oldValueString = "" + evt.getOldValue();
					if (evt.getOldValue() == null) {
						oldValueString = null;
					}

					// plain attribute
					change.withPropertyKind(ChangeEvent.PLAIN).withNewValue("" + attrValue)
							.withOldValue(oldValueString);

					// send it
					send(change);
				}
			}
		}

		return true;
	}

	// ==========================================================================

	protected PropertyChangeSupport listeners = new PropertyChangeSupport(this);

	public PropertyChangeSupport getPropertyChangeSupport() {
		return listeners;
	}

	public boolean addPropertyChangeListener(PropertyChangeListener listener) {
		getPropertyChangeSupport().addPropertyChangeListener(listener);
		return true;
	}

	public boolean addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
		getPropertyChangeSupport().addPropertyChangeListener(propertyName, listener);
		return true;
	}

	public boolean removePropertyChangeListener(PropertyChangeListener listener) {
		getPropertyChangeSupport().removePropertyChangeListener(listener);
		return true;
	}

	// ==========================================================================

	public void removeYou() {
		setModelDBListener(null);
		getPropertyChangeSupport().firePropertyChange("REMOVE_YOU", this, null);
	}

	// ==========================================================================

	public static final String PROPERTY_HOSTNAME = "hostName";

	private String hostName = "localhost";

	public String getHostName() {
		return this.hostName;
	}

	public void setHostName(String value) {
		if (!StrUtil.stringEquals(this.hostName, value)) {

			String oldValue = this.hostName;
			this.hostName = value;
			getPropertyChangeSupport().firePropertyChange(PROPERTY_HOSTNAME, oldValue, value);
		}
	}

	public ModelCouch withHostName(String value) {
		setHostName(value);
		return this;
	}

	@Override
	public String toString() {
		StringBuilder result = new StringBuilder();

		result.append(" ").append(this.getHostName());
		result.append(" ").append(this.getPort());
		return result.substring(1);
	}

	// ==========================================================================

	public static final String PROPERTY_PORT = "port";

	private int port = 5984;

	public int getPort() {
		return this.port;
	}

	public void setPort(int value) {
		if (this.port != value) {

			int oldValue = this.port;
			this.port = value;
			getPropertyChangeSupport().firePropertyChange(PROPERTY_PORT, oldValue, value);
		}
	}

	public ModelCouch withPort(int value) {
		setPort(value);
		return this;
	}

	public IdMap getIdMap() {
		return idMap;
	}

	public ApplicationType getApplicationType() {
		return applicationType;
	}

	public void setApplicationType(ApplicationType applicationType) {
		this.applicationType = applicationType;
	}

	public ModelCouch withApplicationType(ApplicationType applicationType) {
		setApplicationType(applicationType);
		return this;
	}

	public void setIdMap(IdMap idMap) {
		this.idMap = idMap;
	}

	public ModelCouch withIdMap(IdMap idMap) {
		setIdMap(idMap);
		return this;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public ModelCouch withUserName(String userName) {
		setUserName(userName);
		return this;
	}

	/********************************************************************
	 * 
	 *              one                       one
	 * ModelCouch ----------------------------------- ModelDBListener
	 *              couch                   modelDBListener
	 * 
*/ public static final String PROPERTY_MODELDBLISTENER = "modelDBListener"; private ModelDBListener modelDBListener = null; private ModelDBListener mdbListener; public ModelDBListener getModelDBListener() { return this.modelDBListener; } public boolean setModelDBListener(ModelDBListener value) { boolean changed = false; if (this.modelDBListener != value) { ModelDBListener oldValue = this.modelDBListener; if (this.modelDBListener != null) { this.modelDBListener = null; oldValue.setCouch(null); } this.modelDBListener = value; if (value != null) { value.withCouch(this); } getPropertyChangeSupport().firePropertyChange(PROPERTY_MODELDBLISTENER, oldValue, value); changed = true; } return changed; } public ModelCouch withModelDBListener(ModelDBListener value) { setModelDBListener(value); return this; } public ModelDBListener createModelDBListener() { ModelDBListener value = new ModelDBListener(); withModelDBListener(value); return value; } private boolean continuous = false; public void setContinuous(boolean continuous) { if (this.continuous != continuous) { this.continuous = continuous; } } public ModelCouch withContinuous(boolean continous) { setContinuous(continous); return this; } public boolean isContinuous() { return continuous; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy