Add template for assignment 2
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||
<artifactId>dst</artifactId>
|
||||
<version>2021.1</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>ass2-aop</artifactId>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>DST :: Assignment 2 :: Aspect-oriented Programming</name>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjrt</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,17 @@
|
||||
package dst.ass2.aop;
|
||||
|
||||
/**
|
||||
* Implementations of this interface are executable by the IPluginExecutor.
|
||||
*/
|
||||
public interface IPluginExecutable {
|
||||
|
||||
/**
|
||||
* Called when this plugin is executed.
|
||||
*/
|
||||
void execute();
|
||||
|
||||
/**
|
||||
* Called when the execution of the plugin is interrupted
|
||||
*/
|
||||
void interrupted();
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package dst.ass2.aop;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* The plugin executor interface.
|
||||
*/
|
||||
public interface IPluginExecutor {
|
||||
|
||||
/**
|
||||
* Adds a directory to monitor.
|
||||
* May be called before and also after start has been called.
|
||||
*
|
||||
* @param dir the directory to monitor.
|
||||
*/
|
||||
void monitor(File dir);
|
||||
|
||||
/**
|
||||
* Stops monitoring the specified directory.
|
||||
* May be called before and also after start has been called.
|
||||
*
|
||||
* @param dir the directory which should not be monitored anymore.
|
||||
*/
|
||||
void stopMonitoring(File dir);
|
||||
|
||||
/**
|
||||
* Starts the plugin executor.
|
||||
* All added directories will be monitored and any .jar file processed.
|
||||
* If there are any {@link IPluginExecutable} implementations,
|
||||
* they are executed within own threads.
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Stops the plugin executor.
|
||||
* The monitoring of directories and the execution
|
||||
* of the plugins should stop as soon as possible.
|
||||
*/
|
||||
void stop();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package dst.ass2.aop;
|
||||
|
||||
public class PluginExecutorFactory {
|
||||
|
||||
public static IPluginExecutor createPluginExecutor() {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package dst.ass2.aop.logging;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface Invisible {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package dst.ass2.aop.logging;
|
||||
|
||||
public class LoggingAspect {
|
||||
|
||||
// TODO
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package dst.ass2.aop.management;
|
||||
|
||||
public class ManagementAspect {
|
||||
|
||||
// TODO
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package dst.ass2.aop.management;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface Timeout {
|
||||
long value();
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package dst.ass2.aop.event;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import dst.ass2.aop.IPluginExecutable;
|
||||
|
||||
/**
|
||||
* Events triggered by {@link IPluginExecutable}s.
|
||||
*/
|
||||
public class Event {
|
||||
private final long time = System.currentTimeMillis();
|
||||
private Class<? extends IPluginExecutable> pluginClass;
|
||||
private EventType type;
|
||||
private String message;
|
||||
|
||||
public Event(EventType type, Class<? extends IPluginExecutable> pluginClass, String message) {
|
||||
this.type = type;
|
||||
this.pluginClass = pluginClass;
|
||||
this.message = message;
|
||||
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
int pos = stackTrace[1].getMethodName().equals("<init>") ? 1 : 2;
|
||||
Assert.state(stackTrace[pos].getMethodName().equals("<init>"), "Invalid Event Creation");
|
||||
Assert.state(stackTrace[pos + 1].getClassName().equals(EventBus.class.getName()), "Invalid Event Creation");
|
||||
Assert.state(stackTrace[pos + 1].getMethodName().equals("add"), "Invalid Event Creation");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time when the event occurred.
|
||||
*
|
||||
* @return the event creation time
|
||||
*/
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the plugin that triggered the event.
|
||||
*
|
||||
* @return the plugin type
|
||||
*/
|
||||
public Class<? extends IPluginExecutable> getPluginClass() {
|
||||
return pluginClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the event.
|
||||
*
|
||||
* @return the event type
|
||||
*/
|
||||
public EventType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message of the event
|
||||
*
|
||||
* @return the event message
|
||||
*/
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("Event");
|
||||
sb.append("{time=").append(time);
|
||||
sb.append(", pluginClass=").append(pluginClass);
|
||||
sb.append(", type=").append(type);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package dst.ass2.aop.event;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
|
||||
import dst.ass2.aop.IPluginExecutable;
|
||||
|
||||
/**
|
||||
* Stateful event bus that stores events triggered by executable plugins.
|
||||
* <p/>
|
||||
* Note that this implementation is thread safe.
|
||||
*/
|
||||
public class EventBus {
|
||||
private static final EventBus instance = new EventBus();
|
||||
private final List<Event> events = new ArrayList<Event>();
|
||||
|
||||
public static EventBus getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private EventBus() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all events of the certain type(s).<br/>
|
||||
* If no types are specified, all events are returned instead.
|
||||
*
|
||||
* @param types the event types
|
||||
* @return list of all events of the given types.
|
||||
*/
|
||||
public List<Event> getEvents(EventType... types) {
|
||||
synchronized (events) {
|
||||
if (types == null || types.length == 0) {
|
||||
return new ArrayList<Event>(events);
|
||||
} else {
|
||||
List<Event> list = new ArrayList<Event>();
|
||||
for (Event event : events) {
|
||||
for (EventType type : types) {
|
||||
if (type == event.getType()) {
|
||||
list.add(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the event bus by purging the event history.
|
||||
*/
|
||||
public synchronized void reset() {
|
||||
synchronized (events) {
|
||||
events.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new event of a certain type triggered by the given plugin.
|
||||
*
|
||||
* @param type the event type
|
||||
* @param pluginExecutable the plugin that triggered the event
|
||||
* @param message the event message
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void add(EventType type, IPluginExecutable pluginExecutable, String message) {
|
||||
add(type, (Class<? extends IPluginExecutable>) AopUtils.getTargetClass(pluginExecutable), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new event of a certain type triggered by a plugin of the given type.
|
||||
*
|
||||
* @param type the event type
|
||||
* @param pluginType the type of the plugin
|
||||
* @param message the event message
|
||||
*/
|
||||
public void add(EventType type, Class<? extends IPluginExecutable> pluginType, String message) {
|
||||
Event event = new Event(type, pluginType, message);
|
||||
synchronized (events) {
|
||||
events.add(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of events of a certain type fired by this event bus.
|
||||
*
|
||||
* @param type the event type
|
||||
* @return number of events
|
||||
*/
|
||||
public int count(EventType type) {
|
||||
int counter = 0;
|
||||
synchronized (events) {
|
||||
for (Event event : events) {
|
||||
if (event.getType() == type) {
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of events fired so far.
|
||||
*
|
||||
* @return number of events
|
||||
*/
|
||||
public int size() {
|
||||
return events.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there was at least one event of a certain type triggered by a plugin with the given full-qualified
|
||||
* class name.
|
||||
*
|
||||
* If {@code pluginType} is {@code null}, the type of the plugin is not checked. The same is true for {@code type}.
|
||||
* If all parameters are {@code null}, {@code true} is returned if there is at least one event.
|
||||
*
|
||||
* @param pluginType the class name of the plugin
|
||||
* @param type the type of the event
|
||||
* @return {@code true} if there is at least one event matching the criteria, {@code false} otherwise
|
||||
*/
|
||||
public boolean has(String pluginType, EventType type) {
|
||||
synchronized (events) {
|
||||
for (Event event : events) {
|
||||
if ((pluginType == null || pluginType.equals(event.getPluginClass().getName()))
|
||||
&& (type == null || type == event.getType())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package dst.ass2.aop.event;
|
||||
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
import dst.ass2.aop.IPluginExecutable;
|
||||
|
||||
/**
|
||||
* Logging handler that uses the {@link EventBus} for publishing events.
|
||||
*/
|
||||
public class EventBusHandler extends Handler {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void publish(LogRecord record) {
|
||||
if (record.getLoggerName().endsWith("PluginExecutable") && record.getMessage().contains("PluginExecutable")) {
|
||||
try {
|
||||
Class<? extends IPluginExecutable> clazz = (Class<? extends IPluginExecutable>) Class.forName(record.getLoggerName());
|
||||
EventBus.getInstance().add(EventType.INFO, clazz, record.getSourceClassName());
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply does nothing.
|
||||
*/
|
||||
@Override
|
||||
public void flush() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply does nothing.
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package dst.ass2.aop.event;
|
||||
|
||||
public enum EventType {
|
||||
PLUGIN_START, PLUGIN_END, INFO
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package dst.ass2.aop.sample;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
|
||||
import dst.ass2.aop.IPluginExecutable;
|
||||
import dst.ass2.aop.event.EventBus;
|
||||
import dst.ass2.aop.event.EventType;
|
||||
|
||||
public abstract class AbstractPluginExecutable implements IPluginExecutable {
|
||||
@Override
|
||||
public void execute() {
|
||||
EventBus eventBus = EventBus.getInstance();
|
||||
eventBus.add(EventType.PLUGIN_START, this, AopUtils.getTargetClass(this).getSimpleName() + " is executed!");
|
||||
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
// Should not happen but is not critical so the stack trace is printed to grab some attention ;-)
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
eventBus.add(EventType.PLUGIN_END, this, AopUtils.getTargetClass(this).getSimpleName() + " is finished!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interrupted() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package dst.ass2.aop.sample;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
|
||||
import dst.ass2.aop.IPluginExecutable;
|
||||
import dst.ass2.aop.event.EventBus;
|
||||
import dst.ass2.aop.event.EventType;
|
||||
import dst.ass2.aop.logging.Invisible;
|
||||
import dst.ass2.aop.management.Timeout;
|
||||
|
||||
public class InterruptedPluginExecutable implements IPluginExecutable {
|
||||
private boolean interrupted = false;
|
||||
|
||||
@Override
|
||||
@Invisible
|
||||
@Timeout(2000)
|
||||
public void execute() {
|
||||
EventBus eventBus = EventBus.getInstance();
|
||||
eventBus.add(EventType.PLUGIN_START, this, AopUtils.getTargetClass(this).getSimpleName() + " is executed!");
|
||||
|
||||
while (!interrupted) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
// Should not happen but is not critical so the stack trace is printed to grab some attention ;-)
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
eventBus.add(EventType.PLUGIN_END, this, AopUtils.getTargetClass(this).getSimpleName() + " is finished!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interrupted() {
|
||||
interrupted = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package dst.ass2.aop.sample;
|
||||
|
||||
import dst.ass2.aop.logging.Invisible;
|
||||
|
||||
public class InvisiblePluginExecutable extends AbstractPluginExecutable {
|
||||
@Override
|
||||
@Invisible
|
||||
public void execute() {
|
||||
super.execute();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package dst.ass2.aop.sample;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class LoggingPluginExecutable extends AbstractPluginExecutable {
|
||||
@SuppressWarnings("unused")
|
||||
private static Logger log = Logger.getLogger(LoggingPluginExecutable.class
|
||||
.getName());
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package dst.ass2.aop.sample;
|
||||
|
||||
public class SystemOutPluginExecutable extends AbstractPluginExecutable {
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package dst.ass2.aop.tests;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import dst.ass2.aop.IPluginExecutor;
|
||||
import dst.ass2.aop.PluginExecutorFactory;
|
||||
import dst.ass2.aop.event.Event;
|
||||
import dst.ass2.aop.event.EventBus;
|
||||
import dst.ass2.aop.event.EventType;
|
||||
import dst.ass2.aop.util.PluginUtils;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.junit.*;
|
||||
|
||||
public class Ass2_4_1Test {
|
||||
static final String SIMPLE_PLUGIN = "dst.ass2.aop.sample.SimplePluginExecutable";
|
||||
IPluginExecutor executor;
|
||||
EventBus eventBus = EventBus.getInstance();
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
Assert.assertEquals("Cannot create temporary plugin directory: " + PluginUtils.PLUGINS_DIR.getAbsolutePath(),
|
||||
true, PluginUtils.PLUGINS_DIR.isDirectory() || PluginUtils.PLUGINS_DIR.mkdirs());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() throws IOException {
|
||||
FileUtils.forceDeleteOnExit(PluginUtils.PLUGINS_DIR);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
PluginUtils.cleanPluginDirectory();
|
||||
executor = PluginExecutorFactory.createPluginExecutor();
|
||||
executor.monitor(PluginUtils.PLUGINS_DIR);
|
||||
executor.start();
|
||||
eventBus.reset();
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
executor.stop();
|
||||
eventBus.reset();
|
||||
PluginUtils.cleanPluginDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executing plugin copied to plugin directory.
|
||||
*/
|
||||
@Test(timeout = PluginUtils.PLUGIN_TEST_TIMEOUT)
|
||||
public void copiedPlugin_isExecutedCorrectly() throws Exception {
|
||||
// Preparing new plugin
|
||||
PluginUtils.preparePlugin(PluginUtils.SIMPLE_FILE);
|
||||
|
||||
// Periodically check for the plugin to be executed
|
||||
while (eventBus.size() != 2) {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
// Verify that the plugin was started and stopped orderly
|
||||
assertTrue(SIMPLE_PLUGIN + " was not started properly.", eventBus.has(SIMPLE_PLUGIN, EventType.PLUGIN_START));
|
||||
assertTrue(SIMPLE_PLUGIN + " did not finish properly.", eventBus.has(SIMPLE_PLUGIN, EventType.PLUGIN_END));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking that each plugin JAR uses its own ClassLoader.
|
||||
*/
|
||||
@Test(timeout = PluginUtils.PLUGIN_TEST_TIMEOUT)
|
||||
public void samePlugins_useSeparateClassLoaders() throws Exception {
|
||||
// Preparing two plugins
|
||||
PluginUtils.preparePlugin(PluginUtils.SIMPLE_FILE);
|
||||
PluginUtils.preparePlugin(PluginUtils.SIMPLE_FILE);
|
||||
|
||||
// Periodically check for the plugins to be executed
|
||||
while (eventBus.size() != 4) {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify that the plugins were loaded by different classloaders.
|
||||
* This can be checked by comparing the ClassLoaders or comparing the classes themselves.
|
||||
* In other words, if a class is loaded by two different ClassLoaders, it holds that
|
||||
* a.getClass() != b.getClass() even if the byte code is identical.
|
||||
*/
|
||||
List<Event> events = eventBus.getEvents(EventType.PLUGIN_START);
|
||||
String msg = "Both plugins where loaded by the same ClassLoader";
|
||||
assertNotSame(msg, events.get(0).getPluginClass().getClassLoader(), events.get(1).getPluginClass().getClassLoader());
|
||||
assertNotSame(msg, events.get(0).getPluginClass(), events.get(1).getPluginClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking whether two plugins in a single JAR are executed concurrently.
|
||||
*/
|
||||
@Test(timeout = PluginUtils.PLUGIN_TEST_TIMEOUT)
|
||||
public void allPlugins_executeConcurrently() throws Exception {
|
||||
// Start a plugin containing two IPluginExecutable classes
|
||||
PluginUtils.preparePlugin(PluginUtils.ALL_FILE);
|
||||
|
||||
// Periodically check for the plugins to be executed
|
||||
while (eventBus.size() != 4) {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
// Check that there is exactly one start and end event each
|
||||
List<Event> starts = eventBus.getEvents(EventType.PLUGIN_START);
|
||||
List<Event> ends = eventBus.getEvents(EventType.PLUGIN_END);
|
||||
assertEquals("EventBus must contain exactly 2 start events.", 2, starts.size());
|
||||
assertEquals("EventBus must contain exactly 2 end events.", 2, ends.size());
|
||||
|
||||
// Verify that the plugins were started concurrently
|
||||
String msg = "All plugins should have been started before the first ended - %d was after %d.";
|
||||
for (Event end : ends) {
|
||||
for (Event start : starts) {
|
||||
assertTrue(String.format(msg, start.getTime(), end.getTime()), start.getTime() < end.getTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking whether two plugin JARs are executed concurrently.
|
||||
*/
|
||||
@Test(timeout = PluginUtils.PLUGIN_TEST_TIMEOUT)
|
||||
public void multiplePlugins_executeConcurrently() throws Exception {
|
||||
// Start two plugins at once
|
||||
PluginUtils.preparePlugin(PluginUtils.SIMPLE_FILE);
|
||||
PluginUtils.preparePlugin(PluginUtils.SIMPLE_FILE);
|
||||
|
||||
// Periodically check for the plugins to be executed
|
||||
while (eventBus.size() != 4) {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
// Check that there is exactly one start and end event each
|
||||
List<Event> starts = eventBus.getEvents(EventType.PLUGIN_START);
|
||||
List<Event> ends = eventBus.getEvents(EventType.PLUGIN_END);
|
||||
assertEquals("EventBus must contain exactly 2 start events.", 2, starts.size());
|
||||
assertEquals("EventBus must contain exactly 2 end events.", 2, ends.size());
|
||||
|
||||
// Verify that the plugins were started concurrently.
|
||||
String msg = "All plugins should have been started before the first ended - %d was after %d.";
|
||||
for (Event end : ends) {
|
||||
for (Event start : starts) {
|
||||
assertTrue(String.format(msg, start.getTime(), end.getTime()), start.getTime() < end.getTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package dst.ass2.aop.tests;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import dst.ass2.aop.IPluginExecutable;
|
||||
import dst.ass2.aop.event.Event;
|
||||
import dst.ass2.aop.event.EventBus;
|
||||
import dst.ass2.aop.event.EventType;
|
||||
import dst.ass2.aop.sample.InvisiblePluginExecutable;
|
||||
import dst.ass2.aop.sample.LoggingPluginExecutable;
|
||||
import dst.ass2.aop.sample.SystemOutPluginExecutable;
|
||||
import dst.ass2.aop.util.PluginUtils;
|
||||
import org.aspectj.lang.annotation.After;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.aspectj.weaver.internal.tools.PointcutExpressionImpl;
|
||||
import org.aspectj.weaver.tools.ShadowMatch;
|
||||
import org.junit.Test;
|
||||
import org.springframework.aop.PointcutAdvisor;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import dst.ass2.aop.logging.Invisible;
|
||||
import dst.ass2.aop.logging.LoggingAspect;
|
||||
|
||||
public class Ass2_4_2Test {
|
||||
final EventBus eventBus = EventBus.getInstance();
|
||||
|
||||
@org.junit.Before
|
||||
@org.junit.After
|
||||
public void beforeAndAfter() {
|
||||
eventBus.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the {@link LoggingAspect} is a valid AspectJ aspect i.e., {@link Aspect @Aspect} as well as
|
||||
* {@link Around @Around} or {@link Before @Before} / {@link After @After}.
|
||||
*/
|
||||
@Test
|
||||
public void loggingAspect_isValid() {
|
||||
Aspect aspect = AnnotationUtils.findAnnotation(LoggingAspect.class, Aspect.class);
|
||||
assertNotNull("LoggingAspect is not annotated with @Aspect", aspect);
|
||||
|
||||
Map<Method, Around> around = PluginUtils.findMethodAnnotation(LoggingAspect.class, Around.class);
|
||||
Map<Method, Before> before = PluginUtils.findMethodAnnotation(LoggingAspect.class, Before.class);
|
||||
Map<Method, After> after = PluginUtils.findMethodAnnotation(LoggingAspect.class, After.class);
|
||||
|
||||
boolean found = !around.isEmpty() || (!before.isEmpty() && !after.isEmpty());
|
||||
assertTrue("LoggingAspect does not contain methods annotated with @Around OR @Before / @After", found);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the pointcut expression of the {@link LoggingAspect} does not match any method except the
|
||||
* {@link IPluginExecutable#execute()} method.
|
||||
*/
|
||||
@Test
|
||||
public void pointcutExpression_matchesCorrectly() {
|
||||
IPluginExecutable executable = PluginUtils.getExecutable(LoggingPluginExecutable.class, LoggingAspect.class);
|
||||
assertTrue("Executable must implement the Advised interface", executable instanceof Advised);
|
||||
Advised advised = (Advised) executable;
|
||||
|
||||
PointcutAdvisor pointcutAdvisor = PluginUtils.getPointcutAdvisor(advised);
|
||||
assertNotNull("PointcutAdvisor not found because there is no pointcut or the pointcut does not match", pointcutAdvisor);
|
||||
|
||||
String expression = PluginUtils.getBestExpression(advised);
|
||||
assertTrue("Pointcut expression must include '" + IPluginExecutable.class.getName() + "'", expression.contains(IPluginExecutable.class.getName()));
|
||||
assertTrue("Pointcut expression must include '" + PluginUtils.EXECUTE_METHOD.getName() + "'", expression.contains(PluginUtils.EXECUTE_METHOD.getName()));
|
||||
|
||||
PointcutExpressionImpl pointcutExpression = PluginUtils.getPointcutExpression(advised);
|
||||
ShadowMatch shadowMatch = pointcutExpression.matchesMethodExecution(PluginUtils.EXECUTE_METHOD);
|
||||
assertTrue("Pointcut does not match IPluginExecute.execute()", shadowMatch.alwaysMatches());
|
||||
|
||||
shadowMatch = pointcutExpression.matchesMethodExecution(PluginUtils.INTERRUPTED_METHOD);
|
||||
assertTrue("Pointcut must not match IPluginExecute.interrupted()", shadowMatch.neverMatches());
|
||||
|
||||
shadowMatch = pointcutExpression.matchesMethodExecution(ReflectionUtils.findMethod(getClass(), PluginUtils.EXECUTE_METHOD.getName()));
|
||||
assertTrue("Pointcut must not match LoggingPluginTest.execute()", shadowMatch.neverMatches());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the pointcut expression of the LoggingAspect contains the {@link Invisible @Invisible} annotation.
|
||||
*/
|
||||
@Test
|
||||
public void pointcutExpression_containsInvisibleAnnotation() {
|
||||
IPluginExecutable executable = PluginUtils.getExecutable(LoggingPluginExecutable.class, LoggingAspect.class);
|
||||
Advised advised = (Advised) executable;
|
||||
|
||||
String expression = PluginUtils.getBestExpression(advised);
|
||||
String annotationName = Invisible.class.getName();
|
||||
assertTrue("Pointcut expression does not contain " + annotationName, expression.contains(annotationName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the pointcut expression of the {@link LoggingAspect} does not match any method annotated with
|
||||
* {@link Invisible @Invisible}.
|
||||
*/
|
||||
@Test
|
||||
public void pointcutExpression_doesNotMatchInvisible() {
|
||||
IPluginExecutable executable = PluginUtils.getExecutable(LoggingPluginExecutable.class, LoggingAspect.class);
|
||||
Advised advised = (Advised) executable;
|
||||
|
||||
PointcutExpressionImpl pointcutExpression = PluginUtils.getPointcutExpression(advised);
|
||||
|
||||
Method loggingMethod = ReflectionUtils.findMethod(LoggingPluginExecutable.class, PluginUtils.EXECUTE_METHOD.getName());
|
||||
ShadowMatch shadowMatch = pointcutExpression.matchesMethodExecution(loggingMethod);
|
||||
assertTrue("Pointcut does not match LoggingPluginExecutable.execute()", shadowMatch.alwaysMatches());
|
||||
|
||||
Method invisibleMethod = ReflectionUtils.findMethod(InvisiblePluginExecutable.class, PluginUtils.EXECUTE_METHOD.getName());
|
||||
shadowMatch = pointcutExpression.matchesMethodExecution(invisibleMethod);
|
||||
assertTrue("Pointcut matches InvisiblePluginExecutable.execute()", shadowMatch.neverMatches());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the {@link LoggingAspect} uses the {@link java.util.logging.Logger Logger} defined in the plugin.
|
||||
*/
|
||||
@Test
|
||||
public void loggingAspect_usesLogger() {
|
||||
IPluginExecutable executable = PluginUtils.getExecutable(LoggingPluginExecutable.class, LoggingAspect.class);
|
||||
Advised advised = (Advised) executable;
|
||||
|
||||
// Add handler end check that there are no events
|
||||
PluginUtils.addBusHandlerIfNecessary(advised);
|
||||
assertEquals("EventBus must be empty", 0, eventBus.count(EventType.INFO));
|
||||
|
||||
// Execute plugin and check that there are 2 events
|
||||
executable.execute();
|
||||
List<Event> events = eventBus.getEvents(EventType.INFO);
|
||||
assertEquals("EventBus must exactly contain 2 INFO events", 2, events.size());
|
||||
|
||||
// Check if the logger contains the correct class name
|
||||
events = eventBus.getEvents(EventType.INFO);
|
||||
for (Event event : events) {
|
||||
assertEquals("Event message must contain the name of the " + LoggingAspect.class.getSimpleName(), LoggingAspect.class.getName(), event.getMessage());
|
||||
assertSame("Event must be logged for " + LoggingPluginExecutable.class.getSimpleName(), LoggingPluginExecutable.class, event.getPluginClass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the {@link LoggingAspect} uses {@code System.out} if the plugin does not contain a
|
||||
* {@link java.util.logging.Logger Logger} field.
|
||||
*
|
||||
* @throws IllegalAccessException if {@code System.out} cannot be modified (must not happen)
|
||||
*/
|
||||
@Test
|
||||
public void loggingAspect_usesSystemOut() throws IllegalAccessException {
|
||||
// Redirect System.out
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
PrintStream out = PluginUtils.setStaticFinalField(System.class, "out", new PrintStream(byteArrayOutputStream));
|
||||
try {
|
||||
// Execute plugin
|
||||
IPluginExecutable executable = PluginUtils.getExecutable(SystemOutPluginExecutable.class, LoggingAspect.class);
|
||||
assertEquals("EventBus must be empty", 0, eventBus.size());
|
||||
executable.execute();
|
||||
assertEquals("EventBus must exactly contain 2 events", 2, eventBus.size());
|
||||
|
||||
// Verify that the log output contains the class name of the executed plugin
|
||||
String output = byteArrayOutputStream.toString();
|
||||
assertTrue(String.format("Log output must contain %s\n\tbut was%s", SystemOutPluginExecutable.class.getName(), output),
|
||||
output.contains(SystemOutPluginExecutable.class.getName()));
|
||||
} finally {
|
||||
// Reset System.out
|
||||
PluginUtils.setStaticFinalField(System.class, "out", out);
|
||||
}
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package dst.ass2.aop.tests;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import dst.ass2.aop.IPluginExecutable;
|
||||
import dst.ass2.aop.event.Event;
|
||||
import dst.ass2.aop.event.EventBus;
|
||||
import dst.ass2.aop.sample.InterruptedPluginExecutable;
|
||||
import dst.ass2.aop.util.PluginUtils;
|
||||
import org.aspectj.lang.annotation.After;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.aspectj.weaver.internal.tools.PointcutExpressionImpl;
|
||||
import org.aspectj.weaver.tools.ShadowMatch;
|
||||
import org.junit.Test;
|
||||
import org.springframework.aop.PointcutAdvisor;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import dst.ass2.aop.management.ManagementAspect;
|
||||
|
||||
public class Ass2_4_3Test {
|
||||
final EventBus eventBus = EventBus.getInstance();
|
||||
|
||||
@org.junit.Before
|
||||
@org.junit.After
|
||||
public void beforeAndAfter() {
|
||||
eventBus.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the {@link ManagementAspect} is a valid AspectJ aspect i.e., {@link Aspect @Aspect} as well as
|
||||
* {@link Around @Around} or {@link Before @Before} / {@link After @After}.
|
||||
*/
|
||||
@Test
|
||||
public void managementAspect_isValid() {
|
||||
Aspect aspect = AnnotationUtils.findAnnotation(ManagementAspect.class, Aspect.class);
|
||||
assertNotNull("ManagementAspect is not annotated with @Aspect", aspect);
|
||||
|
||||
Map<Method, Around> around = PluginUtils.findMethodAnnotation(ManagementAspect.class, Around.class);
|
||||
Map<Method, Before> before = PluginUtils.findMethodAnnotation(ManagementAspect.class, Before.class);
|
||||
Map<Method, After> after = PluginUtils.findMethodAnnotation(ManagementAspect.class, After.class);
|
||||
|
||||
boolean found = !around.isEmpty() || (!before.isEmpty() && !after.isEmpty());
|
||||
assertEquals("ManagementAspect does not contain methods annotated with @Around OR @Before and @After", true, found);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the pointcut expression of the {@link ManagementAspect}
|
||||
* does not match any method except the {@link IPluginExecutable#execute()} method.
|
||||
*/
|
||||
@Test
|
||||
public void pointcutExpression_matchesCorrectly() {
|
||||
IPluginExecutable executable = PluginUtils.getExecutable(InterruptedPluginExecutable.class, ManagementAspect.class);
|
||||
assertEquals("Executable must implement the Advised interface", true, executable instanceof Advised);
|
||||
Advised advised = (Advised) executable;
|
||||
|
||||
PointcutAdvisor pointcutAdvisor = PluginUtils.getPointcutAdvisor(advised);
|
||||
assertNotNull("PointcutAdvisor not found because there is no pointcut or the pointcut does not match", pointcutAdvisor);
|
||||
|
||||
PointcutExpressionImpl pointcutExpression = PluginUtils.getPointcutExpression(advised);
|
||||
Method interruptedMethod = ReflectionUtils.findMethod(InterruptedPluginExecutable.class, PluginUtils.EXECUTE_METHOD.getName());
|
||||
ShadowMatch shadowMatch = pointcutExpression.matchesMethodExecution(interruptedMethod);
|
||||
assertEquals("Pointcut does not match InterruptedPluginExecutable.execute()", true, shadowMatch.alwaysMatches());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the {@link ManagementAspect} interrupts the plugin after the given timeout.
|
||||
*/
|
||||
@Test(timeout = PluginUtils.PLUGIN_TEST_TIMEOUT)
|
||||
public void managementAspect_interruptsCorrectly() {
|
||||
IPluginExecutable executable = PluginUtils.getExecutable(InterruptedPluginExecutable.class, ManagementAspect.class);
|
||||
assertEquals("EventBus must be empty", 0, eventBus.size());
|
||||
executable.execute();
|
||||
|
||||
List<Event> events = eventBus.getEvents();
|
||||
assertEquals("EventBus must contain 2 events", 2, events.size());
|
||||
|
||||
long duration = events.get(1).getTime() - events.get(0).getTime();
|
||||
assertTrue("Plugin was not interrupted 2 seconds after starting it", duration < 3000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package dst.ass2.aop.tests;
|
||||
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
|
||||
@RunWith(Suite.class)
|
||||
@Suite.SuiteClasses({
|
||||
Ass2_4_1Test.class,
|
||||
Ass2_4_2Test.class,
|
||||
Ass2_4_3Test.class
|
||||
})
|
||||
public class Ass2_4_Suite {
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package dst.ass2.aop.util;
|
||||
|
||||
import static org.apache.commons.io.FileUtils.openOutputStream;
|
||||
import static org.apache.commons.io.IOUtils.copy;
|
||||
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.join;
|
||||
import static org.springframework.util.ClassUtils.CLASS_FILE_SUFFIX;
|
||||
import static org.springframework.util.ClassUtils.convertClassNameToResourcePath;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.apache.commons.io.input.AutoCloseInputStream;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
/**
|
||||
* Builds plugin JARs on demand.
|
||||
*
|
||||
* This class is for internal purposes only.
|
||||
* Note that the {@link #main(String...)} method can be adjusted to create other plugins.
|
||||
*/
|
||||
public final class JarUtils {
|
||||
private JarUtils() {
|
||||
}
|
||||
|
||||
public static void main(String... args) throws IOException {
|
||||
String path = join(args, " ");
|
||||
File dir = new File(defaultIfBlank(path, "ass2-aop/src/test/resources"));
|
||||
|
||||
createJar(new File(dir, "simple.zip"),
|
||||
"dst.ass2.aop.sample.SimplePluginExecutable"
|
||||
);
|
||||
|
||||
createJar(new File(dir, "all.zip"),
|
||||
"dst.ass2.aop.sample.SimplePluginExecutable",
|
||||
"dst.ass2.aop.sample.IgnoredPluginExecutable"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new JAR file containing the given classes.
|
||||
*
|
||||
* @param jarFile the destination JAR file
|
||||
* @param classes the classes to add
|
||||
* @throws IOException if an I/O error has occurred
|
||||
*/
|
||||
public static void createJar(File jarFile, String... classes) throws IOException {
|
||||
try (JarOutputStream stream = new JarOutputStream(openOutputStream(jarFile))) {
|
||||
stream.setLevel(ZipOutputStream.STORED);
|
||||
for (String clazz : classes) {
|
||||
String path = convertClassNameToResourcePath(clazz) + CLASS_FILE_SUFFIX;
|
||||
stream.putNextEntry(new JarEntry(path));
|
||||
copy(new AutoCloseInputStream(new ClassPathResource(path).getInputStream()), stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
package dst.ass2.aop.util;
|
||||
|
||||
import static org.apache.commons.io.filefilter.FileFileFilter.FILE;
|
||||
import static org.apache.commons.io.filefilter.FileFilterUtils.and;
|
||||
import static org.apache.commons.io.filefilter.FileFilterUtils.or;
|
||||
import static org.apache.commons.io.filefilter.FileFilterUtils.prefixFileFilter;
|
||||
import static org.springframework.util.ReflectionUtils.findField;
|
||||
import static org.springframework.util.ReflectionUtils.findMethod;
|
||||
import static org.springframework.util.ReflectionUtils.getField;
|
||||
import static org.springframework.util.ReflectionUtils.makeAccessible;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import dst.ass2.aop.event.EventBusHandler;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.aspectj.weaver.internal.tools.PointcutExpressionImpl;
|
||||
import org.aspectj.weaver.patterns.Pointcut;
|
||||
import org.springframework.aop.Advisor;
|
||||
import org.springframework.aop.PointcutAdvisor;
|
||||
import org.springframework.aop.aspectj.AbstractAspectJAdvice;
|
||||
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
|
||||
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import dst.ass2.aop.IPluginExecutable;
|
||||
|
||||
/**
|
||||
* Contains some utility methods for plugins.
|
||||
*/
|
||||
public final class PluginUtils {
|
||||
|
||||
public final static int PLUGIN_TEST_TIMEOUT = 30000;
|
||||
|
||||
public static final File PLUGINS_DIR = new File(
|
||||
FileUtils.getTempDirectoryPath(), "plugins_"
|
||||
+ System.currentTimeMillis());
|
||||
|
||||
public static File ALL_FILE;
|
||||
public static File SIMPLE_FILE;
|
||||
|
||||
public static final Method EXECUTE_METHOD = findMethod(
|
||||
IPluginExecutable.class, "execute");
|
||||
public static final Method INTERRUPTED_METHOD = findMethod(
|
||||
IPluginExecutable.class, "interrupted");
|
||||
|
||||
static {
|
||||
try {
|
||||
ALL_FILE = new ClassPathResource("all.zip").getFile();
|
||||
SIMPLE_FILE = new ClassPathResource("simple.zip").getFile();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot locate plugin in classpath", e);
|
||||
}
|
||||
}
|
||||
|
||||
private PluginUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the value of a static (final) field and returns the previous
|
||||
* value.
|
||||
*
|
||||
* @param clazz the class containing the static field
|
||||
* @param name the name of the field
|
||||
* @param value the value to set
|
||||
* @return the previous value
|
||||
* @throws IllegalAccessException if the field is inaccessible
|
||||
* (should not be the case, since it is set to accessible manually)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T setStaticFinalField(Class<?> clazz, String name, T value)
|
||||
throws IllegalAccessException {
|
||||
// Retrieve the desired field
|
||||
Field field = findField(clazz, name);
|
||||
field.setAccessible(true);
|
||||
|
||||
// Remove the final modifier (if necessary)
|
||||
if (Modifier.isFinal(field.getModifiers())) {
|
||||
Field modifiers = findField(field.getClass(), "modifiers");
|
||||
makeAccessible(modifiers);
|
||||
modifiers.set(field, (Integer) modifiers.get(field)
|
||||
& ~Modifier.FINAL);
|
||||
}
|
||||
|
||||
// Get the current value
|
||||
T current = (T) field.get(null);
|
||||
|
||||
// Set the new value
|
||||
field.set(null, value);
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new unique {@link File} object within the {@link #PLUGINS_DIR}
|
||||
* directory.
|
||||
*
|
||||
* @return the file
|
||||
*/
|
||||
public static File uniqueFile() {
|
||||
return new File(PLUGINS_DIR, "_" + System.nanoTime() + ".jar");
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the given file to a file in the plugin directory.<br/>
|
||||
*
|
||||
* @throws IOException if the destination file already exists or the file was not copied
|
||||
* @see #uniqueFile()
|
||||
*/
|
||||
public static void preparePlugin(File file) throws IOException {
|
||||
File destFile = uniqueFile();
|
||||
if (destFile.exists()) {
|
||||
throw new IOException("Destination file must not exist.");
|
||||
}
|
||||
|
||||
File tempFile = new File(destFile.getParentFile(), "tmp_"
|
||||
+ UUID.randomUUID().toString());
|
||||
FileUtils.copyFile(file, tempFile);
|
||||
if (!tempFile.renameTo(destFile) || !destFile.isFile()) {
|
||||
throw new IOException(String.format(
|
||||
"File '%s' was not copied to '%s'.", file, destFile));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all plugin JARs copied to the plugin directory.
|
||||
*/
|
||||
public static void cleanPluginDirectory() {
|
||||
FileFilter filter = and(FILE,
|
||||
or(prefixFileFilter("_"), prefixFileFilter("tmp_")));
|
||||
System.gc();
|
||||
|
||||
for (File file : PLUGINS_DIR.listFiles(filter)) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ads a new {@link EventBusHandler} to the logger declared within the given
|
||||
* objects class if necessary.<br/>
|
||||
* This method does nothing if the logger already has an
|
||||
* {@link EventBusHandler} or there is no logger.
|
||||
*
|
||||
* @param obj the object
|
||||
*/
|
||||
public static void addBusHandlerIfNecessary(Object obj) {
|
||||
Class<?> targetClass = AopUtils.getTargetClass(obj);
|
||||
Field field = findField(targetClass, null, Logger.class);
|
||||
if (field != null) {
|
||||
makeAccessible(field);
|
||||
Logger logger = (Logger) getField(field, obj);
|
||||
for (Handler handler : logger.getHandlers()) {
|
||||
if (handler instanceof EventBusHandler) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
logger.addHandler(new EventBusHandler());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of the given {@link IPluginExecutable} class and
|
||||
* returns a proxy with the AspectJ aspect applied to it.<br/>
|
||||
* If {@code aspectClass} is {@code null}, no aspect is applied.
|
||||
*
|
||||
* @param clazz the plugin class
|
||||
* @param aspectClass the class containing AspectJ definitions
|
||||
* @return proxy of the plugin instance
|
||||
*/
|
||||
public static IPluginExecutable getExecutable(
|
||||
Class<? extends IPluginExecutable> clazz, Class<?> aspectClass) {
|
||||
IPluginExecutable target = BeanUtils.instantiateClass(clazz);
|
||||
AspectJProxyFactory factory = new AspectJProxyFactory(target);
|
||||
factory.setProxyTargetClass(true);
|
||||
if (aspectClass != null) {
|
||||
factory.addAspect(BeanUtils.instantiateClass(aspectClass));
|
||||
}
|
||||
return factory.getProxy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pointcut expression of the given advised proxy.
|
||||
*
|
||||
* @param advised the proxy with the applied aspect
|
||||
* @return the pointcut expression or {@code null} if none was found
|
||||
*/
|
||||
public static PointcutExpressionImpl getPointcutExpression(Advised advised) {
|
||||
PointcutAdvisor pointcutAdvisor = getPointcutAdvisor(advised);
|
||||
if (pointcutAdvisor != null) {
|
||||
AspectJExpressionPointcut pointcut = (AspectJExpressionPointcut) pointcutAdvisor
|
||||
.getPointcut();
|
||||
if (pointcut.getPointcutExpression() instanceof PointcutExpressionImpl) {
|
||||
return (PointcutExpressionImpl) pointcut
|
||||
.getPointcutExpression();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pointcut advisor of the given proxy if its advice part is an
|
||||
* {@link AbstractAspectJAdvice}.
|
||||
*
|
||||
* @param advised the proxy with the applied aspect
|
||||
* @return the pointcut advisor or {@code null} if there is no AspectJ pointcut advisor applied
|
||||
*/
|
||||
public static PointcutAdvisor getPointcutAdvisor(Advised advised) {
|
||||
for (Advisor advisor : advised.getAdvisors()) {
|
||||
if (advisor instanceof PointcutAdvisor
|
||||
&& advisor.getAdvice() instanceof AbstractAspectJAdvice) {
|
||||
return (PointcutAdvisor) advisor;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to resolve all parts of the pointcut expression of the aspect
|
||||
* applied to the given proxy.
|
||||
*
|
||||
* @param advised the proxy with the applied aspect
|
||||
* @return a string representation of this pointcut expression
|
||||
* @see #getPointcutExpression(org.springframework.aop.framework.Advised)
|
||||
* @see #getPointcutAdvisor(org.springframework.aop.framework.Advised)
|
||||
*/
|
||||
public static String getBestExpression(Advised advised) {
|
||||
PointcutExpressionImpl pointcutExpression = getPointcutExpression(advised);
|
||||
if (pointcutExpression != null) {
|
||||
Pointcut underlyingPointcut = pointcutExpression
|
||||
.getUnderlyingPointcut();
|
||||
if (findMethod(underlyingPointcut.getClass(), "toString")
|
||||
.getDeclaringClass() != Object.class) {
|
||||
return underlyingPointcut.toString();
|
||||
}
|
||||
return pointcutExpression.getPointcutExpression();
|
||||
}
|
||||
PointcutAdvisor advisor = getPointcutAdvisor(advised);
|
||||
AspectJExpressionPointcut pointcut = (AspectJExpressionPointcut) advisor
|
||||
.getPointcut();
|
||||
return pointcut.getExpression();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all public methods of the given class annotated with a certain
|
||||
* annotation.
|
||||
*
|
||||
* @param clazz the class
|
||||
* @param annotationType the annotation to search for
|
||||
* @return methods annotated with the given annotation
|
||||
*/
|
||||
public static <A extends Annotation> Map<Method, A> findMethodAnnotation(
|
||||
Class<?> clazz, Class<A> annotationType) {
|
||||
Map<Method, A> map = new HashMap<Method, A>();
|
||||
for (Method method : clazz.getDeclaredMethods()) {
|
||||
A annotation = AnnotationUtils.findAnnotation(method,
|
||||
annotationType);
|
||||
if (annotation != null) {
|
||||
map.put(method, annotation);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user