/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* bstefanescu
*
* $Id$
*/
package org.eclipse.ecr.core.api.operation;
import java.io.PrintStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import org.eclipse.ecr.core.api.CoreSession;
import org.eclipse.ecr.core.api.DocumentRef;
/**
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*
*/
public abstract class Operation<T> implements Serializable {
private static final long serialVersionUID = -9191284967153046156L;
// the current command
private static final ThreadLocal<Operation<?>> operation = new ThreadLocal<Operation<?>>();
public static final String START_EVENT = "commandStarted";
public static final String TERMINATE_EVENT = "commandTerminated";
public static final String OPERATION_START = "!OPERATION_START!";
public static final String OPERATION_END = "!OPERATION_END!";
/**
* Operation flags.
* The first 2 bits are reserved to operation execution state
* The first 8 bits are reserved (bits 0-7)
*/
// No flags are set - operation was not yet started
public static final int NONE = 0;
// set if operation is running (bit 0)
public static final int RUNNING = 1;
// set if operation completed (bit 1)
public static final int TERMINATED = 2;
// an internal operation - not triggered directly by clients
public static final int INTERNAL = 4;
// priority flag - if set this operation is urgent (should not be queued, neither it's completion notification)
public static final int URGENT = 8;
// this operation may run asynchronously - this is a hint
public static final int ASYNC = 16;
// operation completion notification should not be sent over JMS
public static final int BLOCK_JMS = 32;
// Whether or not the data field contains keyed data.
public static final int KEYED_DATA = 64;
// block children events
public static final int BLOCK_CHILD_NOTIFICATIONS = 128;
// reserved by the core for future use
public static final int RESERVED = 256;
/**
* User flags may be used by clients to set custom flags on the operation.
* These flags must use only the range of bits from 8 to 31 (the first byte is reserved for core use)
*/
// mask for user flags
public static final int USER_FLAGS = 0XFFFE00;
/**
* A convenience method to compute the correct user flag from the 0 based representation of that bit
* @param n
* @return
*/
public static int USER_FLAG(int n) { return n << 16; }
protected final String name;
protected int flags;
protected Object data;
protected Status status = Status.STATUS_OK;
protected T result;
private ModificationSet modifs;
protected transient Operation<?> parent;
protected transient CoreSession session;
protected Operation(String name, int flags) {
this.name = name;
this.flags = flags;
}
protected Operation(String name) {
this(name, NONE);
}
public static Operation<?> getCurrent() {
return operation.get();
}
public static Operation<?>[] getStack() {
Operation<?> cmd = operation.get();
if (cmd == null) {
return new Operation<?>[0];
} else if (cmd.parent == null) {
return new Operation<?>[] {cmd};
} else {
List<Operation<?>> cmds = new ArrayList<Operation<?>>();
cmd.fillCommandStack(cmds);
return cmds.toArray(new Operation<?>[cmds.size()]);
}
}
public static Operation<?>[] printStack(PrintStream out) {
Operation<?> cmd = operation.get();
if (cmd == null) {
return new Operation<?>[0];
} else if (cmd.parent == null) {
return new Operation<?>[] {cmd};
} else {
List<Operation<?>> cmds = new ArrayList<Operation<?>>();
cmd.fillCommandStack(cmds);
return cmds.toArray(new Operation<?>[cmds.size()]);
}
}
public final Status getStatus() {
return status;
}
public final int getFlags() {
return flags;
}
public final void setFlags(int flags) {
this.flags |= flags;
}
public final void clearFlags(int flags) {
this.flags &= ~flags;
}
public final boolean isRunning() {
return (flags & RUNNING) == RUNNING;
}
public final boolean isTerminated() {
return (flags & TERMINATED) == TERMINATED;
}
public final boolean isFlagSet(int flag) {
return (flags & flag) == flag;
}
public final T getResult() {
return result;
}
public final String getName() {
return name;
}
// /**
// * TODO impl this?
// * @param args
// */
// public void setArguments(Object ... args) {
//
// }
//
// /**
// * TODO impl this?
// * @return command arguments.
// */
// public Object[] getArguments() {
// return null;
// }
public final Operation<?> getParent() {
return parent;
}
public final CoreSession getSession() {
return session;
}
public T run(CoreSession session, OperationHandler handler,
ProgressMonitor monitor) {
if (isRunning()) {
throw new IllegalStateException("Command was already executed");
}
this.session = session;
result = null;
start();
boolean isNotificationEnabled = isNotificationEnabled();
if (handler != null && isNotificationEnabled) {
handler.startOperation(this);
}
if (monitor != null) {
monitor.started(this);
}
try {
result = doRun(monitor);
} catch (Throwable t) {
status = new Status(Status.ERROR, t);
} finally {
if (handler != null && isNotificationEnabled) {
handler.endOperation(this);
}
end();
if (monitor != null) {
monitor.terminated(this);
}
}
return result;
}
private boolean isNotificationEnabled() {
while (parent != null) {
if (parent.isFlagSet(BLOCK_CHILD_NOTIFICATIONS)) {
return false; // notifications were blocked by a parent
}
parent = parent.parent;
}
// notifications are enabled
return true;
}
protected long startedTime;
private void start() {
setFlags(RUNNING);
parent = operation.get();
if (parent != null) { // inherits client cache context from parent
startedTime = parent.startedTime;
} else {
startedTime = Calendar.getInstance().getTimeInMillis();
}
operation.set(this);
}
protected long endedTime;
private void end() {
operation.set(parent);
setFlags(TERMINATED);
endedTime = Calendar.getInstance().getTimeInMillis();
}
public Date getStartedDate() {
return new Date(startedTime);
}
public Date getEndedDate() {
return new Date(endedTime);
}
public long getDuration() {
return endedTime - startedTime;
}
public boolean isStartedBefore(long time) {
return startedTime <= time;
}
public List<Operation<?>> getCommandStack() {
List<Operation<?>> cmds = new ArrayList<Operation<?>>();
fillCommandStack(cmds);
return cmds;
}
public void fillCommandStack(List<Operation<?>> cmds) {
if (parent != null) {
fillCommandStack(cmds);
}
cmds.add(this);
}
public void printCommandStack(PrintStream out) {
if (parent != null) {
parent.printCommandStack(out);
}
out.println(toString());
}
@Override
public String toString() {
return name;
}
public void addModification(Modification modif) {
if (modifs == null) {
modifs = new ModificationSet();
initModificationSet(modifs);
}
modifs.add(modif);
}
public void addModification(DocumentRef ref, int modifType) {
if (modifs == null) {
modifs = new ModificationSet();
initModificationSet(modifs);
}
modifs.add(ref, modifType);
}
public ModificationSet getModifications() {
if (modifs == null) {
modifs = new ModificationSet();
initModificationSet(modifs);
}
return modifs;
}
protected void initModificationSet(ModificationSet modifs) {
// do nothing by default
}
public abstract T doRun(ProgressMonitor montior) throws Exception;
// application data support
public Object getData() {
return (flags & KEYED_DATA) != 0 ? ((Object []) data) [0] : data;
}
public Object getData(String key) {
if (key == null) {
throw new IllegalArgumentException("Data Key must not be null");
}
if ((flags & KEYED_DATA) != 0) {
Object [] table = (Object []) data;
for (int i = 1; i < table.length; i += 2) {
if (key.equals(table[i])) {
return table[i + 1];
}
}
}
return null;
}
public void setData(Object value) {
if ((flags & KEYED_DATA) != 0) {
((Object []) data) [0] = value;
} else {
data = value;
}
}
public void setData(String key, Object value) {
if (key == null) {
throw new IllegalArgumentException("Data Key must not be null");
}
int index = 1;
Object [] table = null;
if ((flags & KEYED_DATA) != 0) {
table = (Object []) data;
while (index < table.length) {
if (key.equals(table[index])) {
break;
}
index += 2;
}
}
if (value != null) {
if ((flags & KEYED_DATA) != 0) {
if (index == table.length) {
Object [] newTable = new Object [table.length + 2];
System.arraycopy(table, 0, newTable, 0, table.length);
data = table = newTable;
}
} else {
table = new Object [3];
table [0] = data;
data = table;
flags |= KEYED_DATA;
}
table [index] = key;
table [index + 1] = value;
} else {
if ((flags & KEYED_DATA) != 0) {
if (index != table.length) {
int length = table.length - 2;
if (length == 1) {
data = table [0];
flags &= ~KEYED_DATA;
} else {
Object [] newTable = new Object [length];
System.arraycopy(table, 0, newTable, 0, index);
System.arraycopy(table, index + 2, newTable, index, length - index);
data = newTable;
}
}
}
}
}
}