diff --git a/shell-api/src/main/java/org/jboss/forge/shell/command/CommandScoped.java b/shell-api/src/main/java/org/jboss/forge/shell/command/CommandScoped.java
new file mode 100644
index 0000000000..828c203565
--- /dev/null
+++ b/shell-api/src/main/java/org/jboss/forge/shell/command/CommandScoped.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Eclipse Public License version 1.0, available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.jboss.forge.shell.command;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.enterprise.context.NormalScope;
+
+import org.jboss.forge.shell.plugins.Command;
+
+/**
+ * Declares a bean as being scoped to the current {@link Command}. Beans using this scope will be destroyed when the
+ * current {@link Command} finishes executing. The scope is active as long as the command is being executed.
+ *
+ * @author George Gastaldi
+ */
+@NormalScope(passivating = false)
+@Inherited
+@Documented
+@Target({ TYPE, METHOD, FIELD })
+@Retention(value = RetentionPolicy.RUNTIME)
+public @interface CommandScoped
+{
+
+}
diff --git a/shell-api/src/main/java/org/jboss/forge/shell/events/CommandExecuted.java b/shell-api/src/main/java/org/jboss/forge/shell/events/CommandExecuted.java
index 511a2d9a28..8c57fc15b3 100644
--- a/shell-api/src/main/java/org/jboss/forge/shell/events/CommandExecuted.java
+++ b/shell-api/src/main/java/org/jboss/forge/shell/events/CommandExecuted.java
@@ -7,14 +7,16 @@
package org.jboss.forge.shell.events;
+import java.util.Map;
+
import org.jboss.forge.shell.command.CommandMetadata;
/**
* Fired after a plugin/command has been executed and has finished processing.
- *
+ *
* @author Lincoln Baxter, III
* @author Koen Aers
- *
+ *
*/
public final class CommandExecuted
{
@@ -27,18 +29,16 @@ public enum Status
private CommandMetadata command;
private Object[] parameters;
private String originalStatement;
-
- public CommandExecuted()
- {
- }
+ private Map context;
public CommandExecuted(final Status status, final CommandMetadata command, final String originalStatement,
- Object[] parameters)
+ Object[] parameters, Map context)
{
this.status = status;
this.command = command;
this.originalStatement = originalStatement;
this.parameters = parameters;
+ this.context = context;
}
public Status getStatus()
@@ -60,4 +60,9 @@ public String getOriginalStatement()
{
return originalStatement;
}
+
+ public Map getContext()
+ {
+ return context;
+ }
}
diff --git a/shell-api/src/main/java/org/jboss/forge/shell/events/CommandVetoed.java b/shell-api/src/main/java/org/jboss/forge/shell/events/CommandVetoed.java
new file mode 100644
index 0000000000..28fd08572d
--- /dev/null
+++ b/shell-api/src/main/java/org/jboss/forge/shell/events/CommandVetoed.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Eclipse Public License version 1.0, available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.jboss.forge.shell.events;
+
+import java.util.Map;
+
+import org.jboss.forge.shell.command.CommandMetadata;
+
+/**
+ * Fired when a command is vetoed
+ *
+ * @author George Gastaldi
+ *
+ */
+public class CommandVetoed
+{
+ private CommandMetadata command;
+ private Object[] parameters;
+ private String originalStatement;
+ private Map context;
+
+ public CommandVetoed(CommandMetadata command, Object[] parameters, String originalStatement,
+ Map context)
+ {
+ super();
+ this.command = command;
+ this.parameters = parameters;
+ this.originalStatement = originalStatement;
+ this.context = context;
+ }
+
+ public CommandMetadata getCommand()
+ {
+ return command;
+ }
+
+ public void setCommand(CommandMetadata command)
+ {
+ this.command = command;
+ }
+
+ public Object[] getParameters()
+ {
+ return parameters;
+ }
+
+ public void setParameters(Object[] parameters)
+ {
+ this.parameters = parameters;
+ }
+
+ public String getOriginalStatement()
+ {
+ return originalStatement;
+ }
+
+ public void setOriginalStatement(String originalStatement)
+ {
+ this.originalStatement = originalStatement;
+ }
+
+ public Map getContext()
+ {
+ return context;
+ }
+}
diff --git a/shell-api/src/main/java/org/jboss/forge/shell/events/PreCommandExecution.java b/shell-api/src/main/java/org/jboss/forge/shell/events/PreCommandExecution.java
index 1db4963097..7b2255c150 100644
--- a/shell-api/src/main/java/org/jboss/forge/shell/events/PreCommandExecution.java
+++ b/shell-api/src/main/java/org/jboss/forge/shell/events/PreCommandExecution.java
@@ -7,11 +7,13 @@
package org.jboss.forge.shell.events;
+import java.util.Map;
+
import org.jboss.forge.shell.command.CommandMetadata;
/**
* Fired before a plugin/command is executed
- *
+ *
* @author George Gastaldi
*/
public final class PreCommandExecution
@@ -20,17 +22,15 @@ public final class PreCommandExecution
private Object[] parameters;
private String originalStatement;
private boolean vetoed;
-
- public PreCommandExecution()
- {
- }
+ private Map context;
public PreCommandExecution(final CommandMetadata command, final String originalStatement,
- Object[] parameters)
+ Object[] parameters, Map context)
{
this.command = command;
this.originalStatement = originalStatement;
this.parameters = parameters;
+ this.context = context;
}
public CommandMetadata getCommand()
@@ -60,4 +60,9 @@ public void veto()
{
this.vetoed = true;
}
+
+ public Map getContext()
+ {
+ return context;
+ }
}
diff --git a/shell/src/main/java/org/jboss/forge/shell/command/CommandScopedContext.java b/shell/src/main/java/org/jboss/forge/shell/command/CommandScopedContext.java
new file mode 100644
index 0000000000..b6920f861b
--- /dev/null
+++ b/shell/src/main/java/org/jboss/forge/shell/command/CommandScopedContext.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2012 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Eclipse Public License version 1.0, available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.jboss.forge.shell.command;
+
+import java.lang.annotation.Annotation;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Stack;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.enterprise.context.ContextNotActiveException;
+import javax.enterprise.context.spi.Context;
+import javax.enterprise.context.spi.Contextual;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.event.Observes;
+import javax.inject.Singleton;
+
+import org.jboss.forge.shell.events.CommandExecuted;
+import org.jboss.forge.shell.events.CommandVetoed;
+import org.jboss.forge.shell.events.PreCommandExecution;
+
+/**
+ * This class provides lifecycle management for {@link CommandScoped} objects
+ *
+ * @author George Gastaldi
+ */
+@Singleton
+public class CommandScopedContext implements Context
+{
+ private final static String COMPONENT_MAP_NAME = CommandScopedContext.class.getName() + ".componentInstanceMap";
+ private final static String CREATIONAL_MAP_NAME = CommandScopedContext.class.getName() + ".creationalInstanceMap";
+ private static final Stack> contextStack = new Stack>();
+
+ private void assertActive()
+ {
+ if (!isActive())
+ {
+ throw new ContextNotActiveException(
+ "Context with scope annotation @CommandScoped is not active since no command is in execution.");
+ }
+ }
+
+ public Map getCurrentContext()
+ {
+ return contextStack.peek();
+ }
+
+ public void create(@Observes final PreCommandExecution execution)
+ {
+ contextStack.push(execution.getContext());
+ }
+
+ public void destroy(@Observes final CommandExecuted event)
+ {
+ destroyCurrentContext();
+ contextStack.pop();
+ }
+
+ public void destroy(@Observes final CommandVetoed event)
+ {
+ destroyCurrentContext();
+ contextStack.pop();
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private void destroyCurrentContext()
+ {
+ Map, Object> componentInstanceMap = getComponentInstanceMap();
+ Map, CreationalContext>> creationalContextMap = getCreationalContextMap();
+
+ if ((componentInstanceMap != null) && (creationalContextMap != null))
+ {
+ for (Entry, Object> componentEntry : componentInstanceMap.entrySet())
+ {
+ Contextual contextual = componentEntry.getKey();
+ Object instance = componentEntry.getValue();
+ CreationalContext creational = creationalContextMap.get(contextual);
+
+ contextual.destroy(instance, creational);
+ }
+ }
+ getCurrentContext().clear();
+ }
+
+ /*
+ * Context Methods
+ */
+
+ @Override
+ public boolean isActive()
+ {
+ return !contextStack.isEmpty();
+ }
+
+ @Override
+ public Class extends Annotation> getScope()
+ {
+ return CommandScoped.class;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T get(final Contextual component)
+ {
+ assertActive();
+ return (T) getComponentInstanceMap().get(component);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T get(final Contextual component, final CreationalContext creationalContext)
+ {
+ assertActive();
+
+ T instance = get(component);
+
+ if (instance == null)
+ {
+ Map, CreationalContext>> creationalContextMap = getCreationalContextMap();
+ Map, Object> componentInstanceMap = getComponentInstanceMap();
+
+ synchronized (componentInstanceMap)
+ {
+ instance = (T) componentInstanceMap.get(component);
+ if (instance == null)
+ {
+ instance = component.create(creationalContext);
+
+ if (instance != null)
+ {
+ componentInstanceMap.put(component, instance);
+ creationalContextMap.put(component, creationalContext);
+ }
+ }
+ }
+ }
+
+ return instance;
+ }
+
+ /*
+ * Helpers for manipulating the Component/Context maps.
+ */
+ @SuppressWarnings("unchecked")
+ private Map, Object> getComponentInstanceMap()
+ {
+ ConcurrentHashMap, Object> map = (ConcurrentHashMap, Object>) getCurrentContext()
+ .get(COMPONENT_MAP_NAME);
+
+ if (map == null)
+ {
+ map = new ConcurrentHashMap, Object>();
+ getCurrentContext().put(COMPONENT_MAP_NAME, map);
+ }
+
+ return map;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map, CreationalContext>> getCreationalContextMap()
+ {
+ Map, CreationalContext>> map = (ConcurrentHashMap, CreationalContext>>) getCurrentContext()
+ .get(CREATIONAL_MAP_NAME);
+
+ if (map == null)
+ {
+ map = new ConcurrentHashMap, CreationalContext>>();
+ getCurrentContext().put(CREATIONAL_MAP_NAME, map);
+ }
+
+ return map;
+ }
+}
\ No newline at end of file
diff --git a/shell/src/main/java/org/jboss/forge/shell/command/CommandScopedExtension.java b/shell/src/main/java/org/jboss/forge/shell/command/CommandScopedExtension.java
new file mode 100644
index 0000000000..a835bbca24
--- /dev/null
+++ b/shell/src/main/java/org/jboss/forge/shell/command/CommandScopedExtension.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Eclipse Public License version 1.0, available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.jboss.forge.shell.command;
+
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.Extension;
+
+/**
+ * An extension to provide {@link CommandScoped} support.
+ *
+ * @author George Gastaldi
+ *
+ */
+public class CommandScopedExtension implements Extension
+{
+
+ public void registerContext(@Observes final AfterBeanDiscovery event)
+ {
+ CommandScopedContext context = new CommandScopedContext();
+ event.addContext(context);
+ }
+
+}
diff --git a/shell/src/main/java/org/jboss/forge/shell/command/Execution.java b/shell/src/main/java/org/jboss/forge/shell/command/Execution.java
index ed54f1c2ae..5fe309a7b1 100644
--- a/shell/src/main/java/org/jboss/forge/shell/command/Execution.java
+++ b/shell/src/main/java/org/jboss/forge/shell/command/Execution.java
@@ -9,6 +9,8 @@
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Set;
import javax.enterprise.context.spi.CreationalContext;
@@ -22,6 +24,7 @@
import org.jboss.forge.shell.events.CommandExecuted;
import org.jboss.forge.shell.events.CommandExecuted.Status;
import org.jboss.forge.shell.events.CommandMissing;
+import org.jboss.forge.shell.events.CommandVetoed;
import org.jboss.forge.shell.events.PreCommandExecution;
import org.jboss.forge.shell.exceptions.CommandExecutionException;
import org.jboss.forge.shell.plugins.AliasLiteral;
@@ -92,7 +95,7 @@ public void perform(final PipeOut pipeOut)
{
paramStaging[i] = Enums.valueOf(parmTypes[i], parameterArray[i]);
}
- else if(parmTypes[i].isArray() && parmTypes[i].getComponentType().isEnum())
+ else if (parmTypes[i].isArray() && parmTypes[i].getComponentType().isEnum())
{
Object[] array = (Object[]) parameterArray[i];
if (array != null)
@@ -143,11 +146,13 @@ else if(parmTypes[i].isArray() && parmTypes[i].getComponentType().isEnum())
Status status = Status.FAILURE;
ClassLoader current = Thread.currentThread().getContextClassLoader();
+ Map executionContext = new HashMap();
boolean vetoed = false;
try
{
Thread.currentThread().setContextClassLoader(plugin.getClass().getClassLoader());
- PreCommandExecution event = new PreCommandExecution(command, originalStatement, parameterArray);
+ PreCommandExecution event = new PreCommandExecution(command, originalStatement, parameterArray,
+ executionContext);
manager.fireEvent(event, new Annotation[0]);
vetoed = event.isVetoed();
if (!vetoed)
@@ -163,11 +168,16 @@ else if(parmTypes[i].isArray() && parmTypes[i].getComponentType().isEnum())
finally
{
Thread.currentThread().setContextClassLoader(current);
- if (!vetoed)
+ if (vetoed)
+ {
+ manager.fireEvent(new CommandVetoed(command, parameterArray, originalStatement, executionContext));
+ }
+ else
{
- manager.fireEvent(new CommandExecuted(status, command, originalStatement, parameterArray),
- new Annotation[0]);
+ manager.fireEvent(new CommandExecuted(status, command, originalStatement, parameterArray,
+ executionContext));
}
+ executionContext.clear();
}
}
}
diff --git a/shell/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/shell/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
index df2440de0c..fedd92af95 100644
--- a/shell/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
+++ b/shell/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -1,4 +1,5 @@
org.jboss.forge.shell.command.CommandLibraryExtension
+org.jboss.forge.shell.command.CommandScopedExtension
org.jboss.forge.shell.project.ProjectScopedExtension
org.jboss.forge.shell.project.resources.ResourceProducerExtension
org.jboss.forge.shell.project.resources.ResourceScopedExtension
\ No newline at end of file
diff --git a/shell/src/test/java/org/jboss/forge/shell/test/command/CommandScopedObject.java b/shell/src/test/java/org/jboss/forge/shell/test/command/CommandScopedObject.java
new file mode 100644
index 0000000000..d50ec81a75
--- /dev/null
+++ b/shell/src/test/java/org/jboss/forge/shell/test/command/CommandScopedObject.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Eclipse Public License version 1.0, available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.jboss.forge.shell.test.command;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+
+import org.jboss.forge.shell.command.CommandScoped;
+
+@CommandScoped
+public class CommandScopedObject
+{
+ private static final AtomicInteger COUNTER = new AtomicInteger();
+ private int value;
+
+ public CommandScopedObject()
+ {
+ }
+
+ @PostConstruct
+ void create()
+ {
+ value = COUNTER.incrementAndGet();
+
+ }
+
+ public int getValue()
+ {
+ return value;
+ }
+
+ @PreDestroy
+ void destroy()
+ {
+ COUNTER.decrementAndGet();
+ }
+}
diff --git a/shell/src/test/java/org/jboss/forge/shell/test/command/CommandScopedPlugin.java b/shell/src/test/java/org/jboss/forge/shell/test/command/CommandScopedPlugin.java
new file mode 100644
index 0000000000..bb9008159e
--- /dev/null
+++ b/shell/src/test/java/org/jboss/forge/shell/test/command/CommandScopedPlugin.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Eclipse Public License version 1.0, available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.jboss.forge.shell.test.command;
+
+import javax.enterprise.inject.Instance;
+import javax.inject.Inject;
+
+import org.jboss.forge.shell.plugins.Alias;
+import org.jboss.forge.shell.plugins.DefaultCommand;
+import org.jboss.forge.shell.plugins.PipeOut;
+import org.jboss.forge.shell.plugins.Plugin;
+
+/**
+ * @author Lincoln Baxter, III
+ */
+@Alias("cmdscope")
+public class CommandScopedPlugin implements Plugin
+{
+
+ @Inject
+ Instance cmdScopedObj;
+
+ @DefaultCommand
+ public void testMock(PipeOut out)
+ {
+ int value = cmdScopedObj.get().getValue();
+ out.println("" + value);
+ }
+}
diff --git a/shell/src/test/java/org/jboss/forge/shell/test/command/CommandScopedTest.java b/shell/src/test/java/org/jboss/forge/shell/test/command/CommandScopedTest.java
new file mode 100644
index 0000000000..65646ea27a
--- /dev/null
+++ b/shell/src/test/java/org/jboss/forge/shell/test/command/CommandScopedTest.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Eclipse Public License version 1.0, available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.jboss.forge.shell.test.command;
+
+import org.jboss.forge.test.AbstractShellTest;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CommandScopedTest extends AbstractShellTest
+{
+
+ @Test
+ public void testCommandScoped() throws Exception
+ {
+ getShell().execute("cmdscope");
+ Assert.assertTrue(getOutput().contains("1"));
+ resetOutput();
+ getShell().execute("cmdscope");
+ Assert.assertTrue(getOutput().contains("1"));
+ }
+}