/**
* Copyright (c) 2011 Cummings Engineering Consultants, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.fourspaces.couchdb;
import java.net.InetAddress;
import java.net.URL;
import net.sf.json.JSONObject;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* This class encapsulates the data for a replication task running on a couch server
* (as returned from the query to "_active_tasks")
*
* Note: The "equals()" method is overidden for this class in order to easily compare two tasks. However the
* "hashCode()" method was not overidden, so this class should not be used inside hash tables.
*
* @author anthony.payne
*
*/
public class ReplicationTask extends CouchTask {
/**
* Logging instance for this class.
*/
static Log log = LogFactory.getLog(Document.class);
/** Used to identify a running task as a replication task */
public static final String TASK_TYPE = "Replication";
/** Used to parse the string holding the task details */
private static final String DELIMITER = " ";
/** JSON key for the source field */
private static final String SOURCE_KEY = "source";
/** JSON key for the target field */
private static final String TARGET_KEY = "target";
/** JSON key for the create_target field */
private static final String CREATE_TARGET_KEY = "create_target";
/** JSON key for the continuous field */
private static final String CONTINUOUS_KEY = "continuous";
/** JSON key for the cancel field */
private static final String CANCEL_KEY = "cancel";
/** Source (DB) of the replication */
private ReplicationTarget source;
/** Target (DB) of the replication */
private ReplicationTarget destination;
/** Flag indicating if this is to be a continuous replication or not */
private boolean continuous;
/** Flag indicating if the target (DB) should be created if it does not exist */
private boolean createTarget;
/** Flag indicating if this task is to be canceled or not */
private boolean cancel;
/**
* Creates a replication task from the task details returned from a DB query about running tasks.
*
* @param task Task details
* @param status Status of task
* @param pid PID of task
*/
public ReplicationTask(final String task, final String status, final String pid) {
super(TASK_TYPE, task, status, pid);
source = null;
destination = null;
continuous = false;
createTarget = false;
cancel = false;
}
/**
* Creates a replication task between the source and destination.
*
* @param source Source for the replication
* @param destination Target for the replication
*/
public ReplicationTask(ReplicationTarget source, ReplicationTarget destination) {
super(TASK_TYPE, null, null, null);
this.source = source;
this.destination = destination;
}
/**
* Initializes the fields based on the data in the "task" string.
* @return
*/
public boolean loadDetailsFromTask() {
if(task == null) {
return false;
}
String[] parts = task.split(DELIMITER);
if(parts.length < 4) {
log.error("Unable to parse replication task: " + task);
return false;
}
// [0] - Task ID
// [1] - source URL
// [2] - "->"
// [3] - destination URL
source = ReplicationTarget.fromUrl(parts[1]);
destination = ReplicationTarget.fromUrl(parts[3]);
if(source == null || destination == null) {
log.error("Unable to extract source and destination details from replication task: " + task);
return false;
}
return true;
}
/**
* @return the source
*/
public ReplicationTarget getSource() {
return source;
}
/**
* @return the destination
*/
public ReplicationTarget getDestination() {
return destination;
}
/**
* @return the continuous
*/
public boolean isContinuous() {
return continuous;
}
/**
* @return The JSON object representing this replication task. Null is returned upon failure.
*/
public JSONObject getCreateRequest() {
final JSONObject object = new JSONObject();
final String source = this.source.buildUrl();
final String destination = this.destination.buildUrl();
if(source == null || destination == null) {
log.error("Unable to build source or destination URL");
return null;
}
object.put(SOURCE_KEY, source);
object.put(TARGET_KEY, destination);
if(createTarget) {
object.put(CREATE_TARGET_KEY, Boolean.TRUE);
}
if(continuous) {
object.put(CONTINUOUS_KEY, Boolean.TRUE);
}
if(cancel) {
object.put(CANCEL_KEY, Boolean.TRUE);
}
return object;
}
/**
* sets the continuous flag.
*/
public void setContinuous() {
continuous = true;
}
/**
* Sets the create target flag
*/
public void setCreateTarget() {
createTarget = true;
}
/**
* Sets the cancel flag
*/
public void setCancel() {
cancel = true;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if(obj instanceof ReplicationTask) {
final ReplicationTask other = (ReplicationTask) obj;
if(source.equals(other.source) && destination.equals(other.destination)) {
return true;
}
}
return false;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
throw new RuntimeException("hashCode() is not supported yet.");
}
/**
* Simple class for encapsulating the details about a source or destination of a replication task.
*
* Note: The "equals()" method is overidden for this class in order to easily compare two targets. However the
* "hashCode()" method was not overidden, so this class should not be used inside hash tables.
*
* @author anthony.payne
*
*/
public static class ReplicationTarget {
private final static String FULL_URL_PREFIX = "http";
private final static String PATH_SEPARATOR = "/";
private final static int PORT_NOT_USED = -1;
/** The item in the database being replicated (could be a DB or a document) */
private String replicatedEntity;
/** The server */
private String server;
/** The Couch port */
private int port;
/** Indicates if this target is remote (true) or local (false) */
private boolean isRemote;
private ReplicationTarget() {
}
/**
* Constructor used for local targets
* @param replicatedEntry
*/
public ReplicationTarget(final String replicatedEntry) {
this(replicatedEntry, null, PORT_NOT_USED);
}
/**
* Constructor used to specify the server and port along with the entity being replicated.
* @param replicatedEntry
* @param server
* @param port
*/
public ReplicationTarget(final String replicatedEntry, final String server, final int port) {
this.replicatedEntity = replicatedEntry;
this.server = server;
this.port = port;
isRemote = (server != null);
}
/**
* Builds the URL for this replication source or destination
* @return String holding the URL
*/
public String buildUrl() {
final StringBuffer buffer = new StringBuffer();
if(isRemote == false) {
buffer.append(replicatedEntity);
} else {
buffer.append(FULL_URL_PREFIX + "://" + server + ":" + port + "/" + replicatedEntity);
}
return buffer.toString();
}
/**
* Constructs a ReplicationTarget given a URL
* @param url URL from a replication task
* @return The replication target instance if successful; null if not
*/
static private ReplicationTarget fromUrl(final String url) {
if(url.startsWith(FULL_URL_PREFIX) == false) {
final ReplicationTarget target = new ReplicationTarget();
target.isRemote = false;
target.port = PORT_NOT_USED;
target.replicatedEntity = url;
target.server = null;
return target;
}
try {
final URL asUrl = new URL(url);
final ReplicationTarget target = new ReplicationTarget();
target.server = asUrl.getHost();
target.port = asUrl.getPort();
target.replicatedEntity = asUrl.getPath();
if(target.replicatedEntity.startsWith(PATH_SEPARATOR)) {
target.replicatedEntity = target.replicatedEntity.substring(PATH_SEPARATOR.length());
}
if(target.replicatedEntity.endsWith(PATH_SEPARATOR)) {
target.replicatedEntity = target.replicatedEntity.substring(0, target.replicatedEntity.length() -
PATH_SEPARATOR.length());
}
target.isRemote = false;
if(target.server != null) {
final InetAddress tempAddress = InetAddress.getByName(target.server);
target.isRemote = !(tempAddress.isLoopbackAddress());
}
log.debug(target.toString());
return target;
} catch(final Exception e) {
log.debug("Failed to create target due to exception, " + e);
}
return null;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("Host: ");
if(server != null) {
buffer.append(server + ", ");
} else {
buffer.append("Not set, ");
}
buffer.append("Port = " + port + ", ");
buffer.append("Path = " + replicatedEntity + ", isRemote = " + isRemote);
return buffer.toString();
}
/**
* @return the replicatedEntity
*/
public String getReplicatedEntity() {
return replicatedEntity;
}
/**
* @return the sourceDatabaseServer
*/
public String getServer() {
return server;
}
/**
* @return the sourceDatabasePort
*/
public int getPort() {
return port;
}
/**
* @return the isRemote
*/
public boolean isRemote() {
return isRemote;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if(obj instanceof ReplicationTarget) {
final ReplicationTarget other = (ReplicationTarget) obj;
if(((replicatedEntity == null && other.replicatedEntity == null) ||
replicatedEntity.equals(other.replicatedEntity)) &&
((server == null && other.server == null) || server.equals(other.server)) &&
port == other.port) {
return true;
}
}
return false;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
throw new RuntimeException("hashCode() is not supported yet.");
}
}
}