Add template for assignment 2
This commit is contained in:
parent
6d5b4b7b48
commit
dd1e95d6aa
45
ass2-aop/pom.xml
Normal file
45
ass2-aop/pom.xml
Normal file
@ -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>
|
||||||
17
ass2-aop/src/main/java/dst/ass2/aop/IPluginExecutable.java
Normal file
17
ass2-aop/src/main/java/dst/ass2/aop/IPluginExecutable.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
40
ass2-aop/src/main/java/dst/ass2/aop/IPluginExecutor.java
Normal file
40
ass2-aop/src/main/java/dst/ass2/aop/IPluginExecutor.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
11
ass2-aop/src/main/java/dst/ass2/aop/logging/Invisible.java
Normal file
11
ass2-aop/src/main/java/dst/ass2/aop/logging/Invisible.java
Normal file
@ -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
|
||||||
|
|
||||||
|
}
|
||||||
12
ass2-aop/src/main/java/dst/ass2/aop/management/Timeout.java
Normal file
12
ass2-aop/src/main/java/dst/ass2/aop/management/Timeout.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
74
ass2-aop/src/test/java/dst/ass2/aop/event/Event.java
Normal file
74
ass2-aop/src/test/java/dst/ass2/aop/event/Event.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
135
ass2-aop/src/test/java/dst/ass2/aop/event/EventBus.java
Normal file
135
ass2-aop/src/test/java/dst/ass2/aop/event/EventBus.java
Normal file
@ -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() {
|
||||||
|
}
|
||||||
|
}
|
||||||
5
ass2-aop/src/test/java/dst/ass2/aop/event/EventType.java
Normal file
5
ass2-aop/src/test/java/dst/ass2/aop/event/EventType.java
Normal file
@ -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 {
|
||||||
|
}
|
||||||
151
ass2-aop/src/test/java/dst/ass2/aop/tests/Ass2_4_1Test.java
Normal file
151
ass2-aop/src/test/java/dst/ass2/aop/tests/Ass2_4_1Test.java
Normal file
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
180
ass2-aop/src/test/java/dst/ass2/aop/tests/Ass2_4_2Test.java
Normal file
180
ass2-aop/src/test/java/dst/ass2/aop/tests/Ass2_4_2Test.java
Normal file
@ -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() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
90
ass2-aop/src/test/java/dst/ass2/aop/tests/Ass2_4_3Test.java
Normal file
90
ass2-aop/src/test/java/dst/ass2/aop/tests/Ass2_4_3Test.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
ass2-aop/src/test/java/dst/ass2/aop/tests/Ass2_4_Suite.java
Normal file
13
ass2-aop/src/test/java/dst/ass2/aop/tests/Ass2_4_Suite.java
Normal file
@ -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 {
|
||||||
|
}
|
||||||
60
ass2-aop/src/test/java/dst/ass2/aop/util/JarUtils.java
Normal file
60
ass2-aop/src/test/java/dst/ass2/aop/util/JarUtils.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
277
ass2-aop/src/test/java/dst/ass2/aop/util/PluginUtils.java
Normal file
277
ass2-aop/src/test/java/dst/ass2/aop/util/PluginUtils.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
ass2-ioc/pom.xml
Normal file
32
ass2-ioc/pom.xml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?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-ioc</artifactId>
|
||||||
|
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>DST :: Assignment 2 :: IoC</name>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.javassist</groupId>
|
||||||
|
<artifactId>javassist</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-core</artifactId>
|
||||||
|
<version>${spring.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
27
ass2-ioc/src/main/java/dst/ass2/ioc/di/IObjectContainer.java
Normal file
27
ass2-ioc/src/main/java/dst/ass2/ioc/di/IObjectContainer.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package dst.ass2.ioc.di;
|
||||||
|
|
||||||
|
import dst.ass2.ioc.di.annotation.Property;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
public interface IObjectContainer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the (mutable) data structure holding property values that can be injected via the
|
||||||
|
* {@link Property} annotation.
|
||||||
|
*
|
||||||
|
* @return A mutable Properties object
|
||||||
|
*/
|
||||||
|
Properties getProperties();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a container-managed object of the given type.
|
||||||
|
*
|
||||||
|
* @param type the type of object
|
||||||
|
* @param <T> the class type
|
||||||
|
* @return the object
|
||||||
|
* @throws InjectionException throw the concrete InjectionException as specified in the assignment
|
||||||
|
*/
|
||||||
|
<T> T getObject(Class<T> type) throws InjectionException;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package dst.ass2.ioc.di;
|
||||||
|
|
||||||
|
import dst.ass2.ioc.di.annotation.Property;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
public interface IObjectContainerFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new IObjectContainer instance that uses the given properties for injecting
|
||||||
|
* {@link Property} instances.
|
||||||
|
*
|
||||||
|
* @param properties a Properties object
|
||||||
|
* @return a new IObjectContainer instance
|
||||||
|
*/
|
||||||
|
IObjectContainer newObjectContainer(Properties properties);
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package dst.ass2.ioc.di;
|
||||||
|
|
||||||
|
public class InjectionException extends RuntimeException {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public InjectionException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InjectionException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InjectionException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package dst.ass2.ioc.di;
|
||||||
|
|
||||||
|
public class InvalidDeclarationException extends InjectionException {
|
||||||
|
public InvalidDeclarationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidDeclarationException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidDeclarationException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package dst.ass2.ioc.di;
|
||||||
|
|
||||||
|
public class ObjectCreationException extends InjectionException {
|
||||||
|
public ObjectCreationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectCreationException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectCreationException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package dst.ass2.ioc.di;
|
||||||
|
|
||||||
|
public class TypeConversionException extends InjectionException {
|
||||||
|
public TypeConversionException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TypeConversionException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TypeConversionException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package dst.ass2.ioc.di.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks a class to be container managed.
|
||||||
|
*/
|
||||||
|
// TODO: add correct retention policy and target type
|
||||||
|
public @interface Component {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The component can either be marked to be a singleton or a prototype via the {@link Scope} enum. The default scope
|
||||||
|
* is singleton.
|
||||||
|
*
|
||||||
|
* @return the scope
|
||||||
|
*/
|
||||||
|
Scope scope() default Scope.SINGLETON;
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package dst.ass2.ioc.di.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize marks a method to be invoked by the container after it has been constructed and all dependencies and
|
||||||
|
* properties have been injected.
|
||||||
|
*/
|
||||||
|
// TODO: add correct retention policy and target type
|
||||||
|
public @interface Initialize {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package dst.ass2.ioc.di.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject marks a field in a class to be autowired by the container.
|
||||||
|
*/
|
||||||
|
// TODO: add correct retention policy and target type
|
||||||
|
public @interface Inject {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks whether the dependency is required or not.
|
||||||
|
*
|
||||||
|
* @return a boolean value
|
||||||
|
*/
|
||||||
|
boolean optional() default false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Explicitly states the class that should be injected by the container into this field. If the target type is Void,
|
||||||
|
* then we assume the targetType is not set, and the container uses the declared field type instead.
|
||||||
|
*
|
||||||
|
* @return a class
|
||||||
|
*/
|
||||||
|
Class<?> targetType() default Void.class;
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package dst.ass2.ioc.di.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A property injects the value of a {@link java.util.Properties} object into a field based on a key.
|
||||||
|
*/
|
||||||
|
// TODO: add correct retention policy and target type
|
||||||
|
public @interface Property {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The key to look up in the container's Properties object.
|
||||||
|
*
|
||||||
|
* @return a key
|
||||||
|
*/
|
||||||
|
String value();
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package dst.ass2.ioc.di.annotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The scope of a {@link Component}.
|
||||||
|
*/
|
||||||
|
public enum Scope {
|
||||||
|
SINGLETON,
|
||||||
|
PROTOTYPE
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package dst.ass2.ioc.di.impl;
|
||||||
|
|
||||||
|
import dst.ass2.ioc.di.IObjectContainer;
|
||||||
|
import dst.ass2.ioc.di.IObjectContainerFactory;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
public final class ObjectContainerFactory implements IObjectContainerFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IObjectContainer newObjectContainer(Properties properties) {
|
||||||
|
// TODO: implement
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
21
ass2-ioc/src/main/java/dst/ass2/ioc/lock/Lock.java
Normal file
21
ass2-ioc/src/main/java/dst/ass2/ioc/lock/Lock.java
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package dst.ass2.ioc.lock;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks a method to be executed by acquiring a named lock.
|
||||||
|
*/
|
||||||
|
// TODO: add correct retention policy and target type
|
||||||
|
public @interface Lock {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the container managed lock.
|
||||||
|
*
|
||||||
|
* @return a name
|
||||||
|
*/
|
||||||
|
String value();
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package dst.ass2.ioc.lock;
|
||||||
|
|
||||||
|
import java.lang.instrument.ClassFileTransformer;
|
||||||
|
import java.lang.instrument.IllegalClassFormatException;
|
||||||
|
import java.security.ProtectionDomain;
|
||||||
|
|
||||||
|
public class LockingInjector implements ClassFileTransformer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] transform(ClassLoader loader, String className,
|
||||||
|
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
|
||||||
|
byte[] classfileBuffer) throws IllegalClassFormatException {
|
||||||
|
|
||||||
|
// TODO transform all @Lock annotated methods of classes with a @Component annotation
|
||||||
|
|
||||||
|
return classfileBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package dst.ass2.ioc.lock;
|
||||||
|
|
||||||
|
import java.lang.instrument.Instrumentation;
|
||||||
|
|
||||||
|
public class LockingInjectorAgent {
|
||||||
|
|
||||||
|
public static void premain(String args, Instrumentation inst) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package dst.ass2.ioc.tests;
|
||||||
|
|
||||||
|
import dst.ass2.ioc.tests.di.DependencyInjectionTest;
|
||||||
|
import dst.ass2.ioc.tests.di.HierarchyTest;
|
||||||
|
import dst.ass2.ioc.tests.di.InitializeTest;
|
||||||
|
import dst.ass2.ioc.tests.di.PropertyInjectionTest;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Suite;
|
||||||
|
|
||||||
|
@RunWith(Suite.class)
|
||||||
|
@Suite.SuiteClasses({
|
||||||
|
DependencyInjectionTest.class,
|
||||||
|
PropertyInjectionTest.class,
|
||||||
|
HierarchyTest.class,
|
||||||
|
InitializeTest.class
|
||||||
|
})
|
||||||
|
public class Ass2_2_1_Suite {
|
||||||
|
// suite
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package dst.ass2.ioc.tests;
|
||||||
|
|
||||||
|
import dst.ass2.ioc.tests.lock.LockingTest;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Suite;
|
||||||
|
|
||||||
|
@RunWith(Suite.class)
|
||||||
|
@Suite.SuiteClasses({
|
||||||
|
LockingTest.class
|
||||||
|
})
|
||||||
|
public class Ass2_2_2_Suite {
|
||||||
|
// suite
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package dst.ass2.ioc.tests.di;
|
||||||
|
|
||||||
|
public class CustomInitializeTest {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: In cases where the object hierarchy has multiple methods annotated with @Initialize or overwrites
|
||||||
|
* previously annotated methods, you should come up with your own behavior. Document the behavior with at least two
|
||||||
|
* unit tests.
|
||||||
|
*/
|
||||||
|
}
|
||||||
@ -0,0 +1,218 @@
|
|||||||
|
package dst.ass2.ioc.tests.di;
|
||||||
|
|
||||||
|
|
||||||
|
import dst.ass2.ioc.di.IObjectContainer;
|
||||||
|
import dst.ass2.ioc.di.IObjectContainerFactory;
|
||||||
|
import dst.ass2.ioc.di.InjectionException;
|
||||||
|
import dst.ass2.ioc.di.InvalidDeclarationException;
|
||||||
|
import dst.ass2.ioc.di.annotation.Component;
|
||||||
|
import dst.ass2.ioc.di.annotation.Inject;
|
||||||
|
import dst.ass2.ioc.di.annotation.Scope;
|
||||||
|
import dst.ass2.ioc.di.impl.ObjectContainerFactory;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNotSame;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
|
|
||||||
|
public class DependencyInjectionTest {
|
||||||
|
|
||||||
|
private IObjectContainerFactory factory;
|
||||||
|
private IObjectContainer container;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
factory = new ObjectContainerFactory();
|
||||||
|
container = factory.newObjectContainer(new Properties());
|
||||||
|
|
||||||
|
if (container == null) {
|
||||||
|
throw new NullPointerException("ObjectContainerFactory did not return an ObjectContainer instance");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static class SimpleSingleton {
|
||||||
|
public String someValue;
|
||||||
|
|
||||||
|
public SimpleSingleton() {
|
||||||
|
// just to verify that the constructor has been called correctly
|
||||||
|
this.someValue = "42";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NotAComponentClass {
|
||||||
|
// a plain class shouldn't be autowired
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SimpleSingleton singleton;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidDeclarationException.class)
|
||||||
|
public void getObject_onNotAnnotatedClass_throwsException() {
|
||||||
|
container.getObject(NotAComponentClass.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static abstract class AbstractComponentClass {
|
||||||
|
// abstract classes can't be instantiated
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InjectionException.class)
|
||||||
|
public void getObject_onAbstractClass_throwsException() throws Exception {
|
||||||
|
container.getObject(AbstractComponentClass.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getObject_onSimpleSingleton_createsObjectCorrectly() {
|
||||||
|
var object = container.getObject(SimpleSingleton.class);
|
||||||
|
assertNotNull(object);
|
||||||
|
assertThat(object, instanceOf(SimpleSingleton.class));
|
||||||
|
assertEquals("constructor was not called correctly", "42", object.someValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getObject_onSimpleSingleton_returnsSameObject() {
|
||||||
|
var object1 = container.getObject(SimpleSingleton.class);
|
||||||
|
var object2 = container.getObject(SimpleSingleton.class);
|
||||||
|
|
||||||
|
assertNotNull(object1);
|
||||||
|
assertNotNull(object2);
|
||||||
|
assertSame(object1, object2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getObject_onDifferentContainer_returnsDifferentObject() {
|
||||||
|
var container1 = factory.newObjectContainer(new Properties());
|
||||||
|
var object1 = container1.getObject(SimpleSingleton.class);
|
||||||
|
|
||||||
|
var container2 = factory.newObjectContainer(new Properties());
|
||||||
|
var object2 = container2.getObject(SimpleSingleton.class);
|
||||||
|
|
||||||
|
assertNotNull(object1);
|
||||||
|
assertNotNull(object2);
|
||||||
|
assertNotSame(object1, object2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component(scope = Scope.PROTOTYPE)
|
||||||
|
public static class SimplePrototype {
|
||||||
|
|
||||||
|
static final AtomicInteger COUNTER = new AtomicInteger(0);
|
||||||
|
|
||||||
|
public int cnt;
|
||||||
|
|
||||||
|
public SimplePrototype() {
|
||||||
|
// just to verify that the constructor has been called correctly
|
||||||
|
cnt = SimplePrototype.COUNTER.incrementAndGet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getObject_onSimplePrototype_returnsDifferentObjects() {
|
||||||
|
SimplePrototype.COUNTER.set(0);
|
||||||
|
var object1 = container.getObject(SimplePrototype.class);
|
||||||
|
var object2 = container.getObject(SimplePrototype.class);
|
||||||
|
|
||||||
|
assertNotNull(object1);
|
||||||
|
assertNotNull(object2);
|
||||||
|
assertNotSame(object1, object2);
|
||||||
|
|
||||||
|
assertEquals(1, object1.cnt);
|
||||||
|
assertEquals(2, object2.cnt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component(scope = Scope.PROTOTYPE)
|
||||||
|
public static class CompositePrototype {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SimpleSingleton simpleSingleton;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getObject_onCompositePrototype_createsObjectCorrectly() {
|
||||||
|
var object = container.getObject(CompositePrototype.class);
|
||||||
|
assertNotNull(object.simpleSingleton);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getObject_onCompositePrototype_injectsPreviouslyCreatedSingleton() {
|
||||||
|
var singleton = container.getObject(SimpleSingleton.class);
|
||||||
|
|
||||||
|
var object = container.getObject(CompositePrototype.class);
|
||||||
|
assertNotNull(object.simpleSingleton);
|
||||||
|
|
||||||
|
assertSame("autowire should use already container managed singletons", singleton, object.simpleSingleton);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static class CompositeSingleton {
|
||||||
|
@Inject
|
||||||
|
SimpleSingleton simpleSingleton;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getObject_onCompositeSingleton_createsObjectGraphCorrectly() {
|
||||||
|
var object = container.getObject(CompositeSingleton.class);
|
||||||
|
|
||||||
|
assertNotNull(object);
|
||||||
|
assertThat(object, instanceOf(CompositeSingleton.class));
|
||||||
|
|
||||||
|
assertNotNull("dependency of CompositeSingleton not initialized", object.simpleSingleton);
|
||||||
|
assertNotNull("dependency of CompositeSingleton not initialized correctly", object.simpleSingleton.someValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IService {
|
||||||
|
int getSomeValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static class ServiceImpl implements IService {
|
||||||
|
private int someValue;
|
||||||
|
|
||||||
|
public ServiceImpl() {
|
||||||
|
someValue = 42;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSomeValue() {
|
||||||
|
return someValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static class ServiceUser {
|
||||||
|
@Inject(targetType = ServiceImpl.class)
|
||||||
|
IService service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getObject_usingTargetType_createsGraphCorrectly() throws Exception {
|
||||||
|
var object = container.getObject(ServiceUser.class);
|
||||||
|
|
||||||
|
assertNotNull(object);
|
||||||
|
assertNotNull("dependency of ServiceUser not initialized", object.service);
|
||||||
|
assertThat("dependency of ServiceUser not initialized correctly", object.service, instanceOf(ServiceImpl.class));
|
||||||
|
assertEquals("dependency of ServiceUser not initialized correctly", 42, object.service.getSomeValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static class InvalidServiceUser {
|
||||||
|
// trying to inject an existing but invalid type (a SimplePrototype is not an IService)
|
||||||
|
@Inject(targetType = SimplePrototype.class)
|
||||||
|
IService service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidDeclarationException.class)
|
||||||
|
public void getObject_onInvalidTargetType_throwsException() {
|
||||||
|
container.getObject(InvalidServiceUser.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
125
ass2-ioc/src/test/java/dst/ass2/ioc/tests/di/HierarchyTest.java
Normal file
125
ass2-ioc/src/test/java/dst/ass2/ioc/tests/di/HierarchyTest.java
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
package dst.ass2.ioc.tests.di;
|
||||||
|
|
||||||
|
import dst.ass2.ioc.di.IObjectContainer;
|
||||||
|
import dst.ass2.ioc.di.IObjectContainerFactory;
|
||||||
|
import dst.ass2.ioc.di.annotation.Component;
|
||||||
|
import dst.ass2.ioc.di.annotation.Inject;
|
||||||
|
import dst.ass2.ioc.di.impl.ObjectContainerFactory;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
|
||||||
|
|
||||||
|
public class HierarchyTest {
|
||||||
|
|
||||||
|
private IObjectContainerFactory factory;
|
||||||
|
private IObjectContainer container;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
factory = new ObjectContainerFactory();
|
||||||
|
container = factory.newObjectContainer(new Properties());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static class SimpleSingleton {
|
||||||
|
public String someValue;
|
||||||
|
|
||||||
|
public SimpleSingleton() {
|
||||||
|
// just to verify that the constructor has been called correctly
|
||||||
|
this.someValue = "42";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class AbstractComponent {
|
||||||
|
@Inject
|
||||||
|
protected SimpleSingleton superSingleton;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static class ConcreteComponent extends AbstractComponent {
|
||||||
|
@Inject
|
||||||
|
private SimpleSingleton privateSingleton;
|
||||||
|
|
||||||
|
public SimpleSingleton getSuperSingleton() {
|
||||||
|
return superSingleton;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleSingleton getPrivateSingleton() {
|
||||||
|
return privateSingleton;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getObject_injectsDependencyOfSuperClassCorrectly() throws Exception {
|
||||||
|
ConcreteComponent component = container.getObject(ConcreteComponent.class);
|
||||||
|
|
||||||
|
assertNotNull("getObject returned null", component);
|
||||||
|
assertNotNull("Dependency of superclass was not injected", component.getSuperSingleton());
|
||||||
|
assertNotNull("Private dependency was not injected", component.getPrivateSingleton());
|
||||||
|
assertSame("Multiple singleton instances", component.getSuperSingleton(), component.getPrivateSingleton());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static class ClassA {
|
||||||
|
@Inject
|
||||||
|
SimpleSingleton singletonA;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static class ClassB extends ClassA {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SimpleSingleton singletonB;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ClassD classD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static class ClassC {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SimpleSingleton singletonC;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static class ClassD extends ClassC {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SimpleSingleton singletonD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the following hierarchy. Getting first D then B should should inject the previously created D into B.
|
||||||
|
*
|
||||||
|
* +-------+ +-------+
|
||||||
|
* | A | | C |
|
||||||
|
* +-------+ +-------+
|
||||||
|
* ^ is a ^ is a
|
||||||
|
* | |
|
||||||
|
* +-------+ uses +-------+
|
||||||
|
* | B | ---> | D |
|
||||||
|
* +-------+ +-------+
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void getObject_initializesHierarchyCorrectly() throws Exception {
|
||||||
|
ClassD d = container.getObject(ClassD.class);
|
||||||
|
|
||||||
|
assertNotNull("getObject returned null for ClassD", d);
|
||||||
|
assertNotNull("ClassD dependency was not injected", d.singletonD);
|
||||||
|
assertNotNull("ClassC dependency was not injected when instantiating ClassD", d.singletonC);
|
||||||
|
|
||||||
|
ClassB b = container.getObject(ClassB.class);
|
||||||
|
assertNotNull("getObject returned null for ClassB", b);
|
||||||
|
assertNotNull("ClassB dependency was not injected", b.singletonB);
|
||||||
|
assertNotNull("ClassA dependency was not injected when instantiating ClassB", b.singletonA);
|
||||||
|
|
||||||
|
assertNotNull("ClassD dependency was not injected into ClassB", b.classD);
|
||||||
|
assertSame("Container did not re-use already initialized ClassD instance", b.classD, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
114
ass2-ioc/src/test/java/dst/ass2/ioc/tests/di/InitializeTest.java
Normal file
114
ass2-ioc/src/test/java/dst/ass2/ioc/tests/di/InitializeTest.java
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package dst.ass2.ioc.tests.di;
|
||||||
|
|
||||||
|
import dst.ass2.ioc.di.IObjectContainer;
|
||||||
|
import dst.ass2.ioc.di.IObjectContainerFactory;
|
||||||
|
import dst.ass2.ioc.di.annotation.Component;
|
||||||
|
import dst.ass2.ioc.di.annotation.Initialize;
|
||||||
|
import dst.ass2.ioc.di.annotation.Inject;
|
||||||
|
import dst.ass2.ioc.di.impl.ObjectContainerFactory;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
|
public class InitializeTest {
|
||||||
|
|
||||||
|
private IObjectContainerFactory factory;
|
||||||
|
private IObjectContainer container;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
factory = new ObjectContainerFactory();
|
||||||
|
container = factory.newObjectContainer(new Properties());
|
||||||
|
|
||||||
|
if (container == null) {
|
||||||
|
throw new NullPointerException("ObjectContainerFactory did not return an ObjectContainer instance");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static class ComponentClass {
|
||||||
|
|
||||||
|
private AtomicInteger initializeCalls = new AtomicInteger(0);
|
||||||
|
|
||||||
|
@Initialize
|
||||||
|
public void myInitMethod() {
|
||||||
|
initializeCalls.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInitializeCalls() {
|
||||||
|
return initializeCalls.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getObject_runsInitializeMethodCorrectly() throws Exception {
|
||||||
|
ComponentClass component = container.getObject(ComponentClass.class);
|
||||||
|
assertEquals("expected exactly one call to myInitMethod", 1, component.getInitializeCalls());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getObject_runsInitializeMethodOnlyOnce() throws Exception {
|
||||||
|
ComponentClass component = container.getObject(ComponentClass.class);
|
||||||
|
assertEquals("expected exactly one call to myInitMethod", 1, component.getInitializeCalls());
|
||||||
|
|
||||||
|
component = container.getObject(ComponentClass.class);
|
||||||
|
assertEquals("expected exactly one call to myInitMethod", 1, component.getInitializeCalls());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static class AnotherComponentClass {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ComponentClass dependency;
|
||||||
|
|
||||||
|
boolean wasNull;
|
||||||
|
|
||||||
|
@Initialize
|
||||||
|
public void myOtherInitMethod() {
|
||||||
|
this.wasNull = dependency == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComponentClass getDependency() {
|
||||||
|
return dependency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getObject_runsInitializeMethodAfterInjection() throws Exception {
|
||||||
|
AnotherComponentClass component = container.getObject(AnotherComponentClass.class);
|
||||||
|
assertFalse("Expected dependency to be injected before calls to @Initialize", component.wasNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getObject_runsInitializeMethodOfDependencyCorrectly() throws Exception {
|
||||||
|
AnotherComponentClass component = container.getObject(AnotherComponentClass.class);
|
||||||
|
assertEquals("expected exactly one call to myInitMethod", 1, component.getDependency().getInitializeCalls());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static class ComponentWithPrivateInitMethod {
|
||||||
|
|
||||||
|
private AtomicInteger initializeCalls = new AtomicInteger(0);
|
||||||
|
|
||||||
|
@Initialize
|
||||||
|
private void myPrivateInitMethod() {
|
||||||
|
initializeCalls.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInitializeCalls() {
|
||||||
|
return initializeCalls.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getObject_runsPrivateInitializeMethodCorrectly() throws Exception {
|
||||||
|
ComponentWithPrivateInitMethod component = container.getObject(ComponentWithPrivateInitMethod.class);
|
||||||
|
assertEquals("expected exactly one call to myPrivateInitMethod", 1, component.getInitializeCalls());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,268 @@
|
|||||||
|
package dst.ass2.ioc.tests.di;
|
||||||
|
|
||||||
|
import dst.ass2.ioc.di.IObjectContainerFactory;
|
||||||
|
import dst.ass2.ioc.di.ObjectCreationException;
|
||||||
|
import dst.ass2.ioc.di.TypeConversionException;
|
||||||
|
import dst.ass2.ioc.di.annotation.Component;
|
||||||
|
import dst.ass2.ioc.di.annotation.Property;
|
||||||
|
import dst.ass2.ioc.di.annotation.Scope;
|
||||||
|
import dst.ass2.ioc.di.impl.ObjectContainerFactory;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.allOf;
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
|
public class PropertyInjectionTest {
|
||||||
|
|
||||||
|
private IObjectContainerFactory factory;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
factory = new ObjectContainerFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
static class StringPropertyClass {
|
||||||
|
@Property("my_string")
|
||||||
|
String value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simple_string_value_injection() {
|
||||||
|
var properties = new Properties();
|
||||||
|
properties.setProperty("my_string", "my string value");
|
||||||
|
var container = factory.newObjectContainer(properties);
|
||||||
|
|
||||||
|
StringPropertyClass obj = container.getObject(StringPropertyClass.class);
|
||||||
|
assertNotNull("getObject(StringPropertyClass.class) returned null", obj);
|
||||||
|
assertEquals("my string value", obj.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ObjectCreationException.class)
|
||||||
|
public void wireMissingProperty_throwsException() {
|
||||||
|
var container = factory.newObjectContainer(new Properties());
|
||||||
|
container.getObject(StringPropertyClass.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
static class IntegerPropertyClass {
|
||||||
|
@Property("my_integer")
|
||||||
|
Integer value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simple_integer_value_injection() {
|
||||||
|
var properties = new Properties();
|
||||||
|
properties.setProperty("my_integer", "42");
|
||||||
|
var container = factory.newObjectContainer(properties);
|
||||||
|
|
||||||
|
IntegerPropertyClass obj = container.getObject(IntegerPropertyClass.class);
|
||||||
|
assertNotNull("getObject(IntegerPropertyClass.class) returned null", obj);
|
||||||
|
assertEquals(Integer.valueOf(42), obj.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = TypeConversionException.class)
|
||||||
|
public void wireInvalidPropertyType_throwsException() {
|
||||||
|
var properties = new Properties();
|
||||||
|
properties.setProperty("my_integer", "fourtytwo");
|
||||||
|
var container = factory.newObjectContainer(properties);
|
||||||
|
|
||||||
|
IntegerPropertyClass obj = container.getObject(IntegerPropertyClass.class);
|
||||||
|
assertNotNull("getObject(IntegerPropertyClass.class) returned null", obj);
|
||||||
|
assertEquals(Integer.valueOf(42), obj.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
static class IntegerPrimitivePropertyClass {
|
||||||
|
@Property("my_integer")
|
||||||
|
int value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simple_integer_primitive_value_injection() {
|
||||||
|
var properties = new Properties();
|
||||||
|
properties.setProperty("my_integer", "42");
|
||||||
|
var container = factory.newObjectContainer(properties);
|
||||||
|
|
||||||
|
IntegerPrimitivePropertyClass obj = container.getObject(IntegerPrimitivePropertyClass.class);
|
||||||
|
assertNotNull("getObject(IntegerPrimitivePropertyClass.class) returned null", obj);
|
||||||
|
assertEquals(42, obj.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
static class MultiplePropertyClass {
|
||||||
|
@Property("my_string")
|
||||||
|
String stringValue;
|
||||||
|
|
||||||
|
@Property("my_integer")
|
||||||
|
int intValue;
|
||||||
|
|
||||||
|
@Property("my_integer")
|
||||||
|
Integer integerValue;
|
||||||
|
|
||||||
|
@Property("my_float")
|
||||||
|
Float floatValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simple_multiple_property_injection() {
|
||||||
|
var properties = new Properties();
|
||||||
|
properties.setProperty("my_string", "my string value");
|
||||||
|
properties.setProperty("my_integer", "42");
|
||||||
|
properties.setProperty("my_float", "3.14");
|
||||||
|
var container = factory.newObjectContainer(properties);
|
||||||
|
|
||||||
|
MultiplePropertyClass obj = container.getObject(MultiplePropertyClass.class);
|
||||||
|
assertNotNull("getObject(MultiplePropertyClass.class) returned null", obj);
|
||||||
|
|
||||||
|
assertEquals("my string value", obj.stringValue);
|
||||||
|
assertEquals(42, obj.intValue);
|
||||||
|
assertEquals(Integer.valueOf(42), obj.integerValue);
|
||||||
|
assertEquals(Float.valueOf(3.14f), obj.floatValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
static class InheritedMultiplePropertyClass extends MultiplePropertyClass {
|
||||||
|
@Property("my_float")
|
||||||
|
Float theSameFloatValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void multiple_property_injection_with_inheritance() {
|
||||||
|
var properties = new Properties();
|
||||||
|
properties.setProperty("my_string", "my string value");
|
||||||
|
properties.setProperty("my_integer", "42");
|
||||||
|
properties.setProperty("my_float", "3.14");
|
||||||
|
var container = factory.newObjectContainer(properties);
|
||||||
|
|
||||||
|
InheritedMultiplePropertyClass obj = container.getObject(InheritedMultiplePropertyClass.class);
|
||||||
|
assertNotNull("getObject(InheritedMultiplePropertyClass.class) returned null", obj);
|
||||||
|
|
||||||
|
assertEquals(Float.valueOf(3.14f), obj.theSameFloatValue);
|
||||||
|
|
||||||
|
// inherited values
|
||||||
|
assertEquals("Property not set correctly through inheritance", "my string value", obj.stringValue);
|
||||||
|
assertEquals("Property not set correctly through inheritance", 42, obj.intValue);
|
||||||
|
assertEquals("Property not set correctly through inheritance", Integer.valueOf(42), obj.integerValue);
|
||||||
|
assertEquals("Property not set correctly through inheritance", Float.valueOf(3.14f), obj.floatValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component(scope = Scope.PROTOTYPE)
|
||||||
|
static class PrototypePropertyClass {
|
||||||
|
|
||||||
|
@Property("my_string")
|
||||||
|
String stringValue;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getPrototypeObject_usesLatestPropertyValue() {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.setProperty("my_string", "initial value");
|
||||||
|
var container = factory.newObjectContainer(properties);
|
||||||
|
|
||||||
|
var object1 = container.getObject(PrototypePropertyClass.class);
|
||||||
|
assertEquals("initial value", object1.stringValue);
|
||||||
|
|
||||||
|
// Properties returned by IObjectContainerFactory should be mutable
|
||||||
|
container.getProperties().setProperty("my_string", "changed value");
|
||||||
|
|
||||||
|
var object2 = container.getObject(PrototypePropertyClass.class);
|
||||||
|
assertEquals("properties should be mutable", "changed value", object2.stringValue);
|
||||||
|
assertEquals("initial value", object1.stringValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Component
|
||||||
|
static class ManyValuesClass {
|
||||||
|
@Property("my_string")
|
||||||
|
String stringValue01;
|
||||||
|
@Property("my_integer")
|
||||||
|
Integer value01;
|
||||||
|
@Property("my_integer")
|
||||||
|
Integer value02;
|
||||||
|
@Property("my_integer")
|
||||||
|
Integer value03;
|
||||||
|
@Property("my_integer")
|
||||||
|
Integer value04;
|
||||||
|
@Property("my_integer")
|
||||||
|
Integer value05;
|
||||||
|
@Property("my_string")
|
||||||
|
String stringValue02;
|
||||||
|
@Property("my_integer")
|
||||||
|
Integer value06;
|
||||||
|
@Property("my_integer")
|
||||||
|
Integer value07;
|
||||||
|
@Property("my_integer")
|
||||||
|
Integer value08;
|
||||||
|
@Property("my_integer")
|
||||||
|
Integer value09;
|
||||||
|
@Property("my_integer")
|
||||||
|
Integer value10;
|
||||||
|
@Property("my_string")
|
||||||
|
String stringValue03;
|
||||||
|
@Property("my_integer")
|
||||||
|
Integer value11;
|
||||||
|
@Property("my_integer")
|
||||||
|
Integer value12;
|
||||||
|
@Property("my_integer")
|
||||||
|
Integer value13;
|
||||||
|
@Property("my_integer")
|
||||||
|
Integer value14;
|
||||||
|
@Property("my_integer")
|
||||||
|
Integer value15;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Test
|
||||||
|
public void mutatePropertiesConcurrently_verifyThreadSafety() throws Exception {
|
||||||
|
var container = factory.newObjectContainer(new Properties());
|
||||||
|
|
||||||
|
Properties properties = container.getProperties();
|
||||||
|
properties.setProperty("my_integer", "1");
|
||||||
|
properties.setProperty("my_string", "some string value");
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||||
|
CountDownLatch latch = new CountDownLatch(10);
|
||||||
|
|
||||||
|
executor.execute(() -> {
|
||||||
|
for (int i = 0; i < 100000; i++) {
|
||||||
|
latch.countDown();
|
||||||
|
properties.setProperty("my_integer", Integer.toString(i));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Future<ManyValuesClass> getObject = executor.submit(() -> {
|
||||||
|
latch.await(); // wait until the property set loop has started and warmed up
|
||||||
|
return container.getObject(ManyValuesClass.class);
|
||||||
|
});
|
||||||
|
|
||||||
|
ManyValuesClass object = getObject.get();
|
||||||
|
executor.shutdown();
|
||||||
|
executor.awaitTermination(2, TimeUnit.SECONDS);
|
||||||
|
executor.shutdownNow();
|
||||||
|
|
||||||
|
assertNotEquals("updated property value was not used", Integer.valueOf(1), object.value01);
|
||||||
|
assertThat("property values are inconsistent indicating non-thread-safe property injection",
|
||||||
|
object.value01, allOf(
|
||||||
|
is(object.value01), is(object.value02), is(object.value03), is(object.value04),
|
||||||
|
is(object.value05), is(object.value06), is(object.value07), is(object.value08),
|
||||||
|
is(object.value09), is(object.value10), is(object.value11), is(object.value12),
|
||||||
|
is(object.value13), is(object.value14), is(object.value15)
|
||||||
|
));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
434
ass2-ioc/src/test/java/dst/ass2/ioc/tests/lock/LockingTest.java
Normal file
434
ass2-ioc/src/test/java/dst/ass2/ioc/tests/lock/LockingTest.java
Normal file
@ -0,0 +1,434 @@
|
|||||||
|
package dst.ass2.ioc.tests.lock;
|
||||||
|
|
||||||
|
import dst.ass2.ioc.di.annotation.Component;
|
||||||
|
import dst.ass2.ioc.lock.Lock;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ConcurrentModificationException;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
|
||||||
|
|
||||||
|
public class LockingTest {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(LockingTest.class);
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ThreadPoolResource executor = new ThreadPoolResource(2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class simulates the use of a shared resource (the semaphore). If the resource is already acquired
|
||||||
|
* by some other thread, then a {@link ConcurrentModificationException} is thrown. We use this behavior to test
|
||||||
|
* various scenarios (whether mutex access is guaranteed when the @Lock value refers to the same lock).
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public static class SimpleLockManaged {
|
||||||
|
|
||||||
|
public Semaphore semaphore;
|
||||||
|
|
||||||
|
public SimpleLockManaged(Semaphore semaphore) {
|
||||||
|
this.semaphore = semaphore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Lock("my_lock")
|
||||||
|
public void useResource() {
|
||||||
|
useResourceImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Lock("my_other_lock")
|
||||||
|
public void useResourceSomeMore() {
|
||||||
|
useResourceImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void useResourceImpl() {
|
||||||
|
LOG.info("{} trying to acquire semaphore", this);
|
||||||
|
boolean acquired = semaphore.tryAcquire();
|
||||||
|
|
||||||
|
if (!acquired) {
|
||||||
|
LOG.info("{} failed to acquire semaphore", this);
|
||||||
|
throw new ConcurrentModificationException("Semaphore was acquired concurrently");
|
||||||
|
}
|
||||||
|
|
||||||
|
// hold the permit for a few ms
|
||||||
|
try {
|
||||||
|
LOG.info("{} holding permit for 500ms", this);
|
||||||
|
Thread.sleep(500);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
LOG.info("{} releasing semaphore", this);
|
||||||
|
semaphore.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mutexTest_onSameClassAndObject_behavesCorrectly() throws Exception {
|
||||||
|
Semaphore semaphore = new Semaphore(1);
|
||||||
|
|
||||||
|
SimpleLockManaged lockManaged = new SimpleLockManaged(semaphore);
|
||||||
|
|
||||||
|
Future<?> r1 = executor.submit(lockManaged::useResource);
|
||||||
|
Future<?> r2 = executor.submit(lockManaged::useResource);
|
||||||
|
|
||||||
|
try {
|
||||||
|
r1.get();
|
||||||
|
r2.get();
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (e.getCause() instanceof ConcurrentModificationException) {
|
||||||
|
throw new AssertionError("Access to shared resource was not mutually exclusive as required", e);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mutexTest_onSameClassButDifferentObject_behavesCorrectly() throws Exception {
|
||||||
|
Semaphore semaphore = new Semaphore(1);
|
||||||
|
|
||||||
|
SimpleLockManaged lockManaged1 = new SimpleLockManaged(semaphore);
|
||||||
|
SimpleLockManaged lockManaged2 = new SimpleLockManaged(semaphore);
|
||||||
|
|
||||||
|
Future<?> r1 = executor.submit(lockManaged1::useResource);
|
||||||
|
Future<?> r2 = executor.submit(lockManaged2::useResource);
|
||||||
|
|
||||||
|
try {
|
||||||
|
r1.get();
|
||||||
|
r2.get();
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (e.getCause() instanceof ConcurrentModificationException) {
|
||||||
|
throw new AssertionError("Access to shared resource was not mutually exclusive as required", e);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nonMutexTest_onSameClassAndObject_behavesCorrectly() throws Exception {
|
||||||
|
Semaphore semaphore = new Semaphore(1);
|
||||||
|
|
||||||
|
SimpleLockManaged lockManaged = new SimpleLockManaged(semaphore);
|
||||||
|
|
||||||
|
Future<?> r1 = executor.submit(lockManaged::useResource);
|
||||||
|
Future<?> r2 = executor.submit(lockManaged::useResourceSomeMore);
|
||||||
|
|
||||||
|
try {
|
||||||
|
r1.get();
|
||||||
|
r2.get();
|
||||||
|
throw new AssertionError("Different locks should allow concurrent access to resources");
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (!(e.getCause() instanceof ConcurrentModificationException)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
// we expected a concurrent modification exception here
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nonMutexTest_onSameClassButDifferentObject_behavesCorrectly() throws Exception {
|
||||||
|
Semaphore semaphore = new Semaphore(1);
|
||||||
|
|
||||||
|
SimpleLockManaged lockManaged1 = new SimpleLockManaged(semaphore);
|
||||||
|
SimpleLockManaged lockManaged2 = new SimpleLockManaged(semaphore);
|
||||||
|
|
||||||
|
Future<?> r1 = executor.submit(lockManaged1::useResource);
|
||||||
|
Future<?> r2 = executor.submit(lockManaged2::useResourceSomeMore);
|
||||||
|
|
||||||
|
try {
|
||||||
|
r1.get();
|
||||||
|
r2.get();
|
||||||
|
throw new AssertionError("Different locks should allow concurrent access to resources");
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (!(e.getCause() instanceof ConcurrentModificationException)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
// we expected a concurrent modification exception here
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static class AnotherSimpleLockManaged {
|
||||||
|
|
||||||
|
private Semaphore semaphore;
|
||||||
|
|
||||||
|
public AnotherSimpleLockManaged(Semaphore semaphore) {
|
||||||
|
this.semaphore = semaphore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Lock("my_lock")
|
||||||
|
protected void useResourceAgain() {
|
||||||
|
LOG.info("{} trying to acquire semaphore", this);
|
||||||
|
boolean acquired = semaphore.tryAcquire();
|
||||||
|
|
||||||
|
if (!acquired) {
|
||||||
|
LOG.info("{} failed to acquire semaphore", this);
|
||||||
|
throw new ConcurrentModificationException("Semaphore was acquired concurrently");
|
||||||
|
}
|
||||||
|
|
||||||
|
// hold the permit for a few ms
|
||||||
|
try {
|
||||||
|
LOG.info("{} holding permit for 500ms", this);
|
||||||
|
Thread.sleep(500);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
LOG.info("{} releasing semaphore", this);
|
||||||
|
semaphore.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mutexTest_onDifferentClass_behavesCorrectly() throws Exception {
|
||||||
|
Semaphore semaphore = new Semaphore(1);
|
||||||
|
|
||||||
|
SimpleLockManaged lockManaged1 = new SimpleLockManaged(semaphore);
|
||||||
|
AnotherSimpleLockManaged lockManaged2 = new AnotherSimpleLockManaged(semaphore);
|
||||||
|
|
||||||
|
Future<?> r1 = executor.submit(lockManaged1::useResource);
|
||||||
|
Future<?> r2 = executor.submit(lockManaged2::useResourceAgain);
|
||||||
|
|
||||||
|
try {
|
||||||
|
r1.get();
|
||||||
|
r2.get();
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (e.getCause() instanceof ConcurrentModificationException) {
|
||||||
|
throw new AssertionError("Access to shared resource was not mutually exclusive as required", e);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is annotated with @Lock, but does not have a @Component annotation, so it should not be instrumented.
|
||||||
|
*/
|
||||||
|
public static class NotLockManaged {
|
||||||
|
|
||||||
|
public Semaphore semaphore;
|
||||||
|
|
||||||
|
public NotLockManaged(Semaphore semaphore) {
|
||||||
|
this.semaphore = semaphore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Lock("my_lock_0")
|
||||||
|
public void useResource() {
|
||||||
|
LOG.info("{} trying to acquire semaphore", this);
|
||||||
|
boolean acquired = semaphore.tryAcquire();
|
||||||
|
|
||||||
|
if (!acquired) {
|
||||||
|
LOG.info("{} failed to acquire semaphore", this);
|
||||||
|
throw new ConcurrentModificationException("Semaphore was acquired concurrently");
|
||||||
|
}
|
||||||
|
|
||||||
|
// hold the permit for a few ms
|
||||||
|
try {
|
||||||
|
LOG.info("{} holding permit for 500ms", this);
|
||||||
|
Thread.sleep(500);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
LOG.info("{} releasing semaphore", this);
|
||||||
|
semaphore.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnNonComponent_shouldNotBeInstrumented() throws Exception {
|
||||||
|
Semaphore semaphore = new Semaphore(1);
|
||||||
|
|
||||||
|
NotLockManaged notManaged = new NotLockManaged(semaphore);
|
||||||
|
|
||||||
|
Future<?> r1 = executor.submit(notManaged::useResource);
|
||||||
|
Future<?> r2 = executor.submit(notManaged::useResource);
|
||||||
|
|
||||||
|
try {
|
||||||
|
r1.get();
|
||||||
|
r2.get();
|
||||||
|
throw new AssertionError("Only classes with the @Component annotation should be instrumented");
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (!(e.getCause() instanceof ConcurrentModificationException)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
// we expected a concurrent modification exception here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static class LockManagedWithParameter {
|
||||||
|
|
||||||
|
@Lock("my_lock_1")
|
||||||
|
protected void useResource(Semaphore semaphore) {
|
||||||
|
LOG.info("{} trying to acquire semaphore", this);
|
||||||
|
boolean acquired = semaphore.tryAcquire();
|
||||||
|
|
||||||
|
if (!acquired) {
|
||||||
|
LOG.info("{} failed to acquire semaphore", this);
|
||||||
|
throw new ConcurrentModificationException("Semaphore was acquired concurrently");
|
||||||
|
}
|
||||||
|
|
||||||
|
// hold the permit for a few ms
|
||||||
|
try {
|
||||||
|
LOG.info("{} holding permit for 500ms", this);
|
||||||
|
Thread.sleep(500);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
LOG.info("{} releasing semaphore", this);
|
||||||
|
semaphore.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mutexTest_withParameter_behavesCorrectly() throws Exception {
|
||||||
|
Semaphore semaphore = new Semaphore(1);
|
||||||
|
|
||||||
|
LockManagedWithParameter lockManaged = new LockManagedWithParameter();
|
||||||
|
|
||||||
|
Future<?> r1 = executor.submit(() -> lockManaged.useResource(semaphore));
|
||||||
|
Future<?> r2 = executor.submit(() -> lockManaged.useResource(semaphore));
|
||||||
|
|
||||||
|
try {
|
||||||
|
r1.get();
|
||||||
|
r2.get();
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (e.getCause() instanceof ConcurrentModificationException) {
|
||||||
|
throw new AssertionError("Access to shared resource was not mutually exclusive as required", e);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static class LockManagedWithReturnValue {
|
||||||
|
|
||||||
|
public Semaphore semaphore;
|
||||||
|
|
||||||
|
public LockManagedWithReturnValue(Semaphore semaphore) {
|
||||||
|
this.semaphore = semaphore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Lock("my_lock")
|
||||||
|
public Integer useResource() {
|
||||||
|
useResourceImpl();
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void useResourceImpl() {
|
||||||
|
LOG.info("{} trying to acquire semaphore", this);
|
||||||
|
boolean acquired = semaphore.tryAcquire();
|
||||||
|
|
||||||
|
if (!acquired) {
|
||||||
|
LOG.info("{} failed to acquire semaphore", this);
|
||||||
|
throw new ConcurrentModificationException("Semaphore was acquired concurrently");
|
||||||
|
}
|
||||||
|
|
||||||
|
// hold the permit for a few ms
|
||||||
|
try {
|
||||||
|
LOG.info("{} holding permit for 500ms", this);
|
||||||
|
Thread.sleep(500);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
LOG.info("{} releasing semaphore", this);
|
||||||
|
semaphore.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mutexTest_withReturnValue_behavesCorrectly() throws Exception {
|
||||||
|
Semaphore semaphore = new Semaphore(1);
|
||||||
|
|
||||||
|
LockManagedWithReturnValue lockManaged = new LockManagedWithReturnValue(semaphore);
|
||||||
|
|
||||||
|
Future<Integer> r1 = executor.submit(lockManaged::useResource);
|
||||||
|
Future<Integer> r2 = executor.submit(lockManaged::useResource);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Integer i1 = r1.get();
|
||||||
|
Integer i2 = r2.get();
|
||||||
|
|
||||||
|
Assert.assertEquals(Integer.valueOf(42), i1);
|
||||||
|
Assert.assertEquals(Integer.valueOf(42), i2);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (e.getCause() instanceof ConcurrentModificationException) {
|
||||||
|
throw new AssertionError("Access to shared resource was not mutually exclusive as required", e);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public static class LockManagedWithParamAndReturnValue {
|
||||||
|
|
||||||
|
private Integer i = 0;
|
||||||
|
|
||||||
|
@Lock("my_lock_2")
|
||||||
|
public Integer checkAndIncrement(Integer test) throws RuntimeException {
|
||||||
|
System.out.println(Thread.currentThread().getName() + " getting value " + i);
|
||||||
|
int x = i;
|
||||||
|
|
||||||
|
if (i < test) {
|
||||||
|
// this condition will always be true in the tests, just to add some logic
|
||||||
|
try {
|
||||||
|
Thread.sleep(500);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
x = x + 1;
|
||||||
|
i = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test verifies that the instrumentation also works when the function has input parameters and return values.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void mutexTest_withParamAndReturnValue_behavesCorrectly() throws Exception {
|
||||||
|
LockManagedWithParamAndReturnValue managed = new LockManagedWithParamAndReturnValue();
|
||||||
|
|
||||||
|
Future<Integer> r1 = executor.submit(() -> managed.checkAndIncrement(10));
|
||||||
|
Future<Integer> r2 = executor.submit(() -> managed.checkAndIncrement(10));
|
||||||
|
|
||||||
|
try {
|
||||||
|
Integer i1 = r1.get();
|
||||||
|
Integer i2 = r2.get();
|
||||||
|
|
||||||
|
Assert.assertNotEquals("Result of concurrent increment is equal, indicating a race condition", i1, i2);
|
||||||
|
if (i1 < i2) {
|
||||||
|
Assert.assertEquals(Integer.valueOf(1), i1);
|
||||||
|
Assert.assertEquals(Integer.valueOf(2), i2);
|
||||||
|
} else {
|
||||||
|
Assert.assertEquals(Integer.valueOf(2), i1);
|
||||||
|
Assert.assertEquals(Integer.valueOf(1), i2);
|
||||||
|
}
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (e.getCause() instanceof ConcurrentModificationException) {
|
||||||
|
throw new AssertionError("Access to shared resource was not mutually exclusive as required", e);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
package dst.ass2.ioc.tests.lock;
|
||||||
|
|
||||||
|
import org.junit.rules.ExternalResource;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple JUnit Rule wrapper of a fixed thread pool {@link ExecutorService} that shuts down after each test execution.
|
||||||
|
*/
|
||||||
|
public class ThreadPoolResource extends ExternalResource {
|
||||||
|
|
||||||
|
private ExecutorService executor;
|
||||||
|
private int nThreads;
|
||||||
|
|
||||||
|
public ThreadPoolResource(int nThreads) {
|
||||||
|
this.nThreads = nThreads;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void before() {
|
||||||
|
executor = Executors.newFixedThreadPool(nThreads);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void after() {
|
||||||
|
shutdownAndAwaitTermination(executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<?> submit(Runnable task) {
|
||||||
|
return executor.submit(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Future<T> submit(Callable<T> task) {
|
||||||
|
return executor.submit(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExecutorService getExecutor() {
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdownAndAwaitTermination(ExecutorService pool) {
|
||||||
|
System.out.println("shutting down pool");
|
||||||
|
pool.shutdown(); // Disable new tasks from being submitted
|
||||||
|
try {
|
||||||
|
// Wait a while for existing tasks to terminate
|
||||||
|
System.out.println("awaiting pool termination");
|
||||||
|
if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||||
|
pool.shutdownNow(); // Cancel currently executing tasks
|
||||||
|
// Wait a while for tasks to respond to being cancelled
|
||||||
|
if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||||
|
System.err.println("Pool did not terminate");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
// (Re-)Cancel if current thread also interrupted
|
||||||
|
pool.shutdownNow();
|
||||||
|
// Preserve interrupt status
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
ass2-service/api/pom.xml
Normal file
47
ass2-service/api/pom.xml
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?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-service-api</artifactId>
|
||||||
|
|
||||||
|
<name>DST :: Assignment 2 :: Service :: API</name>
|
||||||
|
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-protobuf</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-stub</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<extensions>
|
||||||
|
<extension>
|
||||||
|
<groupId>kr.motd.maven</groupId>
|
||||||
|
<artifactId>os-maven-plugin</artifactId>
|
||||||
|
</extension>
|
||||||
|
</extensions>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.xolstice.maven.plugins</groupId>
|
||||||
|
<artifactId>protobuf-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package dst.ass2.service.api.auth;
|
||||||
|
|
||||||
|
public class AuthenticationException extends Exception {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public AuthenticationException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||||
|
super(message, cause, enableSuppression, writableStackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package dst.ass2.service.api.auth;
|
||||||
|
|
||||||
|
public interface IAuthenticationService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to authenticate the user with the given unique email address and the given password in plain text, by
|
||||||
|
* checking the data against the records in the database. If the credentials are successfully authenticated, the
|
||||||
|
* service generates a new authentication token which is stored (with the users email address) in-memory and then
|
||||||
|
* returned.
|
||||||
|
*
|
||||||
|
* @param email the user email
|
||||||
|
* @param password the password
|
||||||
|
* @return a new authentication token
|
||||||
|
* @throws NoSuchUserException if the given user was not found
|
||||||
|
* @throws AuthenticationException if the credentials could not be authenticated
|
||||||
|
*/
|
||||||
|
String authenticate(String email, String password) throws NoSuchUserException, AuthenticationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the password of the given user in the database. Also updates the in-memory cache in a thread-safe way.
|
||||||
|
*
|
||||||
|
* @param email the user email
|
||||||
|
* @param newPassword the new password in plain text.
|
||||||
|
* @throws NoSuchUserException if the given user was not found
|
||||||
|
*/
|
||||||
|
void changePassword(String email, String newPassword) throws NoSuchUserException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the user that is associated with this token. Returns null if the token does not exist.
|
||||||
|
*
|
||||||
|
* @param token an authentication token previously created via {@link #authenticate(String, String)}
|
||||||
|
* @return the user's email address or null
|
||||||
|
*/
|
||||||
|
String getUser(String token);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the given token is valid (i.e., was issued by this service and has not been invalidated).
|
||||||
|
*
|
||||||
|
* @param token the token to validate
|
||||||
|
* @return true if the token is valid, false otherwise
|
||||||
|
*/
|
||||||
|
boolean isValid(String token);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates the given token, i.e., removes it from the cache. Returns false if the token did not exist.
|
||||||
|
*
|
||||||
|
* @param token the token to invalidate
|
||||||
|
* @return true if the token existed and was successfully invalidated, false otherwise
|
||||||
|
*/
|
||||||
|
boolean invalidate(String token);
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package dst.ass2.service.api.auth;
|
||||||
|
|
||||||
|
public class NoSuchUserException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public NoSuchUserException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public NoSuchUserException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NoSuchUserException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NoSuchUserException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NoSuchUserException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||||
|
super(message, cause, enableSuppression, writableStackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package dst.ass2.service.api.auth.rest;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import dst.ass2.service.api.auth.AuthenticationException;
|
||||||
|
import dst.ass2.service.api.auth.NoSuchUserException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The IAuthenticationResource exposes parts of the {@code IAuthenticationService} as a RESTful interface.
|
||||||
|
*/
|
||||||
|
public interface IAuthenticationResource {
|
||||||
|
|
||||||
|
// TODO annotate the class and methods with the correct javax.ws.rs annotations
|
||||||
|
|
||||||
|
Response authenticate(String email, String password)
|
||||||
|
throws NoSuchUserException, AuthenticationException;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package dst.ass2.service.api.match;
|
||||||
|
|
||||||
|
import dst.ass2.service.api.trip.InvalidTripException;
|
||||||
|
import dst.ass2.service.api.trip.MoneyDTO;
|
||||||
|
import dst.ass2.service.api.trip.TripDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of this interface is provided to you by the application.
|
||||||
|
*/
|
||||||
|
public interface IMatchingService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The passed trip needs at least the following properties set: riderId, pickup, destination and fare
|
||||||
|
*
|
||||||
|
* @param trip the trip the MatchingService will calculate a fare for
|
||||||
|
* @return the proposed fare for the given trip
|
||||||
|
* @throws InvalidTripException in case the route cannot be calculated
|
||||||
|
*/
|
||||||
|
MoneyDTO calculateFare(TripDTO trip) throws InvalidTripException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts the trip into the queue for driver matching.
|
||||||
|
* The trip with the specified ID needs at least the following properties set:
|
||||||
|
* riderId, pickup, destination and fare
|
||||||
|
*
|
||||||
|
* @param tripId
|
||||||
|
*/
|
||||||
|
void queueTripForMatching(Long tripId);
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package dst.ass2.service.api.trip;
|
||||||
|
|
||||||
|
public class DriverNotAvailableException extends Exception {
|
||||||
|
|
||||||
|
public DriverNotAvailableException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DriverNotAvailableException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package dst.ass2.service.api.trip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception indicating that a resource that was trying to be accessed does not exist.
|
||||||
|
*/
|
||||||
|
public class EntityNotFoundException extends Exception {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public EntityNotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityNotFoundException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,112 @@
|
|||||||
|
package dst.ass2.service.api.trip;
|
||||||
|
|
||||||
|
public interface ITripService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and persists a Trip, sets the state to CREATED and calculates an initial fare estimation for
|
||||||
|
* the route 'pickupId - destinationId'
|
||||||
|
*
|
||||||
|
* @param riderId the id of the rider, who is planning a trip
|
||||||
|
* @param pickupId the id of the pickupId location
|
||||||
|
* @param destinationId the id of the destinationId location
|
||||||
|
* @return a TripDTO corresponding to the persisted Trip and includes the fare (null if route is invalid)
|
||||||
|
* @throws EntityNotFoundException if the rider or one of the locations doesn't exist
|
||||||
|
*/
|
||||||
|
TripDTO create(Long riderId, Long pickupId, Long destinationId) throws EntityNotFoundException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirms the given trip (i.e., sets the state of the corresponding trip to QUEUED and
|
||||||
|
* puts it into the queue for matching), if possible (i.e., the trip is still in CREATED state)
|
||||||
|
*
|
||||||
|
* @param tripId the trip to confirm
|
||||||
|
* @throws EntityNotFoundException if the trip cannot be found
|
||||||
|
* @throws IllegalStateException in case the trip is not in state CREATED or the rider is null
|
||||||
|
* @throws InvalidTripException in case the fare couldn't be estimated
|
||||||
|
*/
|
||||||
|
void confirm(Long tripId) throws EntityNotFoundException, IllegalStateException, InvalidTripException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a match for the given trip and sets the trip's state to MATCHED, if possible (i.e., if the trips is
|
||||||
|
* QUEUED). Rolls back transaction in case something goes wrong and re-queues the trip for a new match.
|
||||||
|
* You can assume that the locations (pickup, destination and stops) haven't been deleted and will not be deleted
|
||||||
|
* during the execution of this method.
|
||||||
|
*
|
||||||
|
* @param tripId the id of the trip the match will be created for
|
||||||
|
* @param match the match, containing the driver, the vehicle and the fare
|
||||||
|
* @throws EntityNotFoundException in case one of the following doesn't exist anymore: trip, driver or
|
||||||
|
* vehicle
|
||||||
|
* @throws DriverNotAvailableException in case the driver was assigned in the meantime to another customer
|
||||||
|
* @throws IllegalStateException in case the rider of the trip is null or the trip is not in QUEUED state
|
||||||
|
*/
|
||||||
|
void match(Long tripId, MatchDTO match) throws EntityNotFoundException, DriverNotAvailableException, IllegalStateException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completes the trip (i. e., persists a TripInfo object and set the trips state to COMPLETED)
|
||||||
|
*
|
||||||
|
* @param tripId the id of the trip to complete
|
||||||
|
* @param tripInfoDTO the tripInfo to persist
|
||||||
|
* @throws EntityNotFoundException in case the trip doesn't exist
|
||||||
|
*/
|
||||||
|
void complete(Long tripId, TripInfoDTO tripInfoDTO) throws EntityNotFoundException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the given trip (i.e., sets the state to cancelled)
|
||||||
|
*
|
||||||
|
* @param tripId the trip to cancel
|
||||||
|
* @throws EntityNotFoundException in case the trip doesn't exist
|
||||||
|
*/
|
||||||
|
void cancel(Long tripId) throws EntityNotFoundException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the location as a stop if possible (i.e., if the referenced trip is still in the CREATED state and
|
||||||
|
* the given location is not already in the list of stops.)
|
||||||
|
* As a side effect, the list of the passed TripDTO is modified and the fare freshly estimated.
|
||||||
|
* In case the the estimation fails, sets the fare to null.
|
||||||
|
*
|
||||||
|
* You can assume that the passed TripDTO and the Trip entity have the same values
|
||||||
|
* @param trip the trip
|
||||||
|
* @param locationId the location
|
||||||
|
* @return true if the stop was added, otherwise false
|
||||||
|
* @throws EntityNotFoundException in case the location or trip doesn't exist
|
||||||
|
* @throws IllegalStateException in case the trip isn't longer in the CREATED state
|
||||||
|
*/
|
||||||
|
boolean addStop(TripDTO trip, Long locationId) throws EntityNotFoundException, IllegalStateException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the location from the stops only (i.e., if the referenced trip is still in the CREATED state).
|
||||||
|
* As a side effect, the list of the passed TripDTO is modified and the fare freshly estimated.
|
||||||
|
* In case the the estimation fails, sets the fare to null.
|
||||||
|
*
|
||||||
|
* You can assume that the passed TripDTO and the Trip entity have the same values
|
||||||
|
* @param trip the trip
|
||||||
|
* @param locationId the location to remove
|
||||||
|
* @return true if the trip was removed, otherwise false (for example when the location wasn't added to the stops)
|
||||||
|
* @throws EntityNotFoundException in case the location or trip doesn't exist
|
||||||
|
* @throws IllegalStateException in case the trip isn't longer in the CREATED state
|
||||||
|
*/
|
||||||
|
boolean removeStop(TripDTO trip, Long locationId) throws EntityNotFoundException, IllegalStateException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the trip with the given id.
|
||||||
|
*
|
||||||
|
* @param tripId the id of the trip to remove
|
||||||
|
* @throws EntityNotFoundException in case the trip doesn't exist
|
||||||
|
*/
|
||||||
|
void delete(Long tripId) throws EntityNotFoundException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the trip with the given id and returns it as DTO, including the latest fare estimation.
|
||||||
|
*
|
||||||
|
* @param tripId the id of the trip
|
||||||
|
* @return if found the DTO, otherwise null
|
||||||
|
*/
|
||||||
|
TripDTO find(Long tripId);
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package dst.ass2.service.api.trip;
|
||||||
|
|
||||||
|
public class InvalidTripException extends Exception {
|
||||||
|
public InvalidTripException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidTripException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package dst.ass2.service.api.trip;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class MatchDTO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long driverId;
|
||||||
|
private Long vehicleId;
|
||||||
|
|
||||||
|
private MoneyDTO fare;
|
||||||
|
|
||||||
|
public Long getDriverId() {
|
||||||
|
return driverId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDriverId(Long driverId) {
|
||||||
|
this.driverId = driverId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getVehicleId() {
|
||||||
|
return vehicleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVehicleId(Long vehicleId) {
|
||||||
|
this.vehicleId = vehicleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public MoneyDTO getFare() {
|
||||||
|
return fare;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFare(MoneyDTO fare) {
|
||||||
|
this.fare = fare;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package dst.ass2.service.api.trip;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class MoneyDTO {
|
||||||
|
|
||||||
|
private String currency;
|
||||||
|
private BigDecimal value;
|
||||||
|
|
||||||
|
public String getCurrency() {
|
||||||
|
return currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrency(String currency) {
|
||||||
|
this.currency = currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(BigDecimal value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MoneyDTO moneyDTO = (MoneyDTO) o;
|
||||||
|
return Objects.equals(getCurrency(), moneyDTO.getCurrency()) &&
|
||||||
|
Objects.equals(getValue(), moneyDTO.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(getCurrency(), getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MoneyDTO{" +
|
||||||
|
"currency='" + currency + '\'' +
|
||||||
|
", value=" + value +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
package dst.ass2.service.api.trip;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class TripDTO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Long riderId;
|
||||||
|
private Long pickupId;
|
||||||
|
private Long destinationId;
|
||||||
|
private List<Long> stops;
|
||||||
|
private MoneyDTO fare;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getRiderId() {
|
||||||
|
return riderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRiderId(Long riderId) {
|
||||||
|
this.riderId = riderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getPickupId() {
|
||||||
|
return pickupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPickupId(Long pickupId) {
|
||||||
|
this.pickupId = pickupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getDestinationId() {
|
||||||
|
return destinationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDestinationId(Long destinationId) {
|
||||||
|
this.destinationId = destinationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Long> getStops() {
|
||||||
|
if (stops == null) {
|
||||||
|
stops = new LinkedList<>();
|
||||||
|
}
|
||||||
|
return stops;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStops(List<Long> stops) {
|
||||||
|
this.stops = stops;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MoneyDTO getFare() {
|
||||||
|
return fare;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFare(MoneyDTO fare) {
|
||||||
|
this.fare = fare;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
package dst.ass2.service.api.trip;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class TripInfoDTO {
|
||||||
|
|
||||||
|
private Double distance;
|
||||||
|
private Date completed;
|
||||||
|
private MoneyDTO fare;
|
||||||
|
|
||||||
|
public Double getDistance() {
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDistance(Double distance) {
|
||||||
|
this.distance = distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getCompleted() {
|
||||||
|
return completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCompleted(Date completed) {
|
||||||
|
this.completed = completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public MoneyDTO getFare() {
|
||||||
|
return fare;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFare(MoneyDTO fare) {
|
||||||
|
this.fare = fare;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package dst.ass2.service.api.trip.rest;
|
||||||
|
|
||||||
|
import dst.ass2.service.api.trip.*;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface exposes the {@code ITripService} as a RESTful interface.
|
||||||
|
*/
|
||||||
|
public interface ITripServiceResource {
|
||||||
|
|
||||||
|
// TODO annotate the class and methods with the correct javax.ws.rs annotations
|
||||||
|
|
||||||
|
Response createTrip(Long riderId, Long pickupId, Long destinationId)
|
||||||
|
throws EntityNotFoundException, InvalidTripException;
|
||||||
|
|
||||||
|
Response confirm(Long tripId) throws EntityNotFoundException, InvalidTripException;
|
||||||
|
|
||||||
|
Response getTrip(Long tripId) throws EntityNotFoundException;
|
||||||
|
|
||||||
|
Response deleteTrip(Long tripId) throws EntityNotFoundException;
|
||||||
|
|
||||||
|
Response addStop(Long tripId, Long locationId) throws InvalidTripException, EntityNotFoundException;
|
||||||
|
|
||||||
|
Response removeStop(Long tripId, Long locationId) throws InvalidTripException, EntityNotFoundException;
|
||||||
|
|
||||||
|
Response match(Long tripId, MatchDTO matchDTO) throws EntityNotFoundException, DriverNotAvailableException;
|
||||||
|
|
||||||
|
Response complete(Long tripId, TripInfoDTO tripInfoDTO) throws EntityNotFoundException;
|
||||||
|
|
||||||
|
Response cancel(Long tripId) throws EntityNotFoundException;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
// TODO implement authentication service
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
package dst.ass2.proto.auth;
|
||||||
|
|
||||||
|
import io.grpc.MethodDescriptor;
|
||||||
|
import io.grpc.ServiceDescriptor;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.hasItems;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
|
public class ProtoSpecificationTest {
|
||||||
|
|
||||||
|
private ClassLoader cl;
|
||||||
|
public static final String SERVICE_NAME = "dst.ass2.service.api.auth.proto.AuthService";
|
||||||
|
public static final String GRPC_CLASS_NAME = SERVICE_NAME + "Grpc";
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
cl = ProtoSpecificationTest.class.getClassLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void generatedClass_exists() throws Exception {
|
||||||
|
try {
|
||||||
|
cl.loadClass(GRPC_CLASS_NAME);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new AssertionError("Classpath did not contain expected gRPC service class", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void generatedClass_hasMethods() throws Exception {
|
||||||
|
assertThat(getMethodDescriptors().keySet(), hasItems(
|
||||||
|
SERVICE_NAME + "/authenticate",
|
||||||
|
SERVICE_NAME + "/validateToken"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, MethodDescriptor> getMethodDescriptors() throws ClassNotFoundException {
|
||||||
|
return getMethodDescriptors(getServiceDescriptor(cl.loadClass(GRPC_CLASS_NAME)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, MethodDescriptor> getMethodDescriptors(ServiceDescriptor sd) {
|
||||||
|
return sd.getMethods().stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
MethodDescriptor::getFullMethodName,
|
||||||
|
Function.identity()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServiceDescriptor getServiceDescriptor(Class<?> grpcClass) {
|
||||||
|
try {
|
||||||
|
Method getServiceDescriptor = grpcClass.getDeclaredMethod("getServiceDescriptor");
|
||||||
|
getServiceDescriptor.setAccessible(true);
|
||||||
|
return (ServiceDescriptor) getServiceDescriptor.invoke(null);
|
||||||
|
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
|
||||||
|
throw new RuntimeException("Error finding service descriptor in " + grpcClass.getName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
72
ass2-service/auth-client/pom.xml
Normal file
72
ass2-service/auth-client/pom.xml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?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-service-auth-client</artifactId>
|
||||||
|
|
||||||
|
<name>DST :: Assignment 2 :: Service :: Auth Client</name>
|
||||||
|
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||||
|
<artifactId>ass2-service-api</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-protobuf</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-stub</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-netty</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||||
|
<artifactId>ass2-service-auth</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||||
|
<artifactId>ass1-jpa</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<type>test-jar</type>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||||
|
<artifactId>ass2-service-auth</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<type>test-jar</type>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-orm</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
package dst.ass2.service.auth.client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class holds the host and port value used to connect to the gRPC server. The CDI context provides an instance
|
||||||
|
* that you can inject into your implementation of {@link IAuthenticationClient}. The config values are loaded from the
|
||||||
|
* application.properties file.
|
||||||
|
*/
|
||||||
|
public class AuthenticationClientProperties {
|
||||||
|
|
||||||
|
private String host;
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
public AuthenticationClientProperties() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationClientProperties(String host, int port) {
|
||||||
|
this.host = host;
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHost() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHost(String host) {
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPort() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPort(int port) {
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package dst.ass2.service.auth.client;
|
||||||
|
|
||||||
|
import dst.ass2.service.api.auth.AuthenticationException;
|
||||||
|
import dst.ass2.service.api.auth.NoSuchUserException;
|
||||||
|
|
||||||
|
public interface IAuthenticationClient extends AutoCloseable {
|
||||||
|
|
||||||
|
String authenticate(String email, String password) throws NoSuchUserException, AuthenticationException;
|
||||||
|
|
||||||
|
boolean isTokenValid(String token);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shuts down any underlying resource required to maintain this client.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
void close();
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package dst.ass2.service.auth.client.impl;
|
||||||
|
|
||||||
|
import dst.ass2.service.api.auth.AuthenticationException;
|
||||||
|
import dst.ass2.service.api.auth.NoSuchUserException;
|
||||||
|
import dst.ass2.service.auth.client.AuthenticationClientProperties;
|
||||||
|
import dst.ass2.service.auth.client.IAuthenticationClient;
|
||||||
|
|
||||||
|
public class GrpcAuthenticationClient implements IAuthenticationClient {
|
||||||
|
|
||||||
|
// TODO make use of the generated grpc sources to implement a blocking client
|
||||||
|
|
||||||
|
public GrpcAuthenticationClient(AuthenticationClientProperties properties) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String authenticate(String email, String password) throws NoSuchUserException, AuthenticationException {
|
||||||
|
// TODO
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTokenValid(String token) {
|
||||||
|
// TODO
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
package dst.ass2.service.auth.client;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import dst.ass1.jpa.tests.TestData;
|
||||||
|
import dst.ass2.service.api.auth.AuthenticationException;
|
||||||
|
import dst.ass2.service.api.auth.NoSuchUserException;
|
||||||
|
import dst.ass2.service.auth.AuthenticationServiceApplication;
|
||||||
|
import dst.ass2.service.auth.client.impl.GrpcAuthenticationClient;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest(classes = AuthenticationServiceApplication.class)
|
||||||
|
@Transactional
|
||||||
|
@ActiveProfiles({"testdata", "grpc"})
|
||||||
|
public class AuthenticationClientTest {
|
||||||
|
|
||||||
|
@Value("${grpc.port}")
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
private IAuthenticationClient client;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
AuthenticationClientProperties properties = new AuthenticationClientProperties("localhost", port);
|
||||||
|
client = new GrpcAuthenticationClient(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = NoSuchUserException.class)
|
||||||
|
public void authenticate_invalidUser_throwsException() throws Exception {
|
||||||
|
client.authenticate("nonexisting@example.com", "foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = AuthenticationException.class)
|
||||||
|
public void authenticate_invalidPassword_throwsException() throws Exception {
|
||||||
|
client.authenticate(TestData.RIDER_1_EMAIL, "foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticate_existingUser_returnsToken() throws Exception {
|
||||||
|
String token = client.authenticate(TestData.RIDER_1_EMAIL, TestData.RIDER_1_PW);
|
||||||
|
assertNotNull(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isTokenValid_invalidToken_returnsFalse() throws Exception {
|
||||||
|
boolean valid = client.isTokenValid(UUID.randomUUID().toString()); // should be false in *almost* all cases ;-)
|
||||||
|
assertFalse(valid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isTokenValid_onCreatedToken_returnsTrue() throws Exception {
|
||||||
|
String token = client.authenticate(TestData.RIDER_2_EMAIL, TestData.RIDER_2_PW);
|
||||||
|
boolean valid = client.isTokenValid(token);
|
||||||
|
assertTrue(valid);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
80
ass2-service/auth/pom.xml
Normal file
80
ass2-service/auth/pom.xml
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<?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-service-auth</artifactId>
|
||||||
|
|
||||||
|
<name>DST :: Assignment 2 :: Service :: Auth Server</name>
|
||||||
|
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||||
|
<artifactId>ass1-jpa</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||||
|
<artifactId>ass2-service-api</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-protobuf</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-stub</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-netty</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||||
|
<artifactId>ass1-jpa</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<type>test-jar</type>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-orm</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<extensions>
|
||||||
|
<extension>
|
||||||
|
<groupId>kr.motd.maven</groupId>
|
||||||
|
<artifactId>os-maven-plugin</artifactId>
|
||||||
|
</extension>
|
||||||
|
</extensions>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.xolstice.maven.plugins</groupId>
|
||||||
|
<artifactId>protobuf-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
package dst.ass2.service.auth;
|
||||||
|
|
||||||
|
import dst.ass2.service.api.auth.AuthenticationException;
|
||||||
|
import dst.ass2.service.api.auth.IAuthenticationService;
|
||||||
|
import dst.ass2.service.api.auth.NoSuchUserException;
|
||||||
|
|
||||||
|
public interface ICachingAuthenticationService extends IAuthenticationService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Instead of checking database records directly, the method first checks the cache for existing users. If the user
|
||||||
|
* is not in the cache, then the service checks the database for the given email address, and updates the cache if
|
||||||
|
* necessary.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
String authenticate(String email, String password) throws NoSuchUserException, AuthenticationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads user data from the database into memory.
|
||||||
|
*/
|
||||||
|
void loadData();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the data cached from the database.
|
||||||
|
*/
|
||||||
|
void clearCache();
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package dst.ass2.service.auth.grpc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class holds the port value used to bind the gRPC server. The CDI context provides an instance that you can
|
||||||
|
* inject into your implementation of {@link IGrpcServerRunner}. The config values are loaded from the
|
||||||
|
* grpc.properties.
|
||||||
|
*/
|
||||||
|
public class GrpcServerProperties {
|
||||||
|
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
public GrpcServerProperties() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public GrpcServerProperties(int port) {
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPort() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPort(int port) {
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package dst.ass2.service.auth.grpc;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of this interface is expected by the application to start the grpc server. Inject get
|
||||||
|
* {@link GrpcServerProperties} to access the configuration.
|
||||||
|
*/
|
||||||
|
public interface IGrpcServerRunner {
|
||||||
|
/**
|
||||||
|
* Starts the gRPC server.
|
||||||
|
*
|
||||||
|
* @throws IOException start error
|
||||||
|
*/
|
||||||
|
void run() throws IOException;
|
||||||
|
}
|
||||||
14
ass2-service/auth/src/main/resources/logback.xml
Normal file
14
ass2-service/auth/src/main/resources/logback.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<configuration>
|
||||||
|
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} - %highlight(%5p) [%12.12thread] %cyan(%-40.40logger{39}): %m%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="${log.level:-INFO}">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package dst.ass2.service.auth;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableTransactionManagement
|
||||||
|
public class AuthenticationServiceApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(AuthenticationServiceApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,117 @@
|
|||||||
|
package dst.ass2.service.auth;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.PersistenceContext;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
|
import org.springframework.boot.SpringBootConfiguration;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.context.annotation.PropertySource;
|
||||||
|
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||||
|
import org.springframework.orm.jpa.LocalEntityManagerFactoryBean;
|
||||||
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
|
||||||
|
import dst.ass1.jpa.dao.IDAOFactory;
|
||||||
|
import dst.ass1.jpa.dao.impl.DAOFactory;
|
||||||
|
import dst.ass1.jpa.model.IModelFactory;
|
||||||
|
import dst.ass1.jpa.model.impl.ModelFactory;
|
||||||
|
import dst.ass1.jpa.tests.TestData;
|
||||||
|
import dst.ass1.jpa.util.Constants;
|
||||||
|
import dst.ass2.service.auth.grpc.GrpcServerProperties;
|
||||||
|
|
||||||
|
@SpringBootConfiguration
|
||||||
|
@PropertySource("classpath:/dst/ass2/service/auth/grpc.properties")
|
||||||
|
public class AuthenticationServiceApplicationConfig {
|
||||||
|
|
||||||
|
@PersistenceContext
|
||||||
|
private EntityManager em;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public IModelFactory modelFactory() {
|
||||||
|
return new ModelFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public IDAOFactory daoFactory() {
|
||||||
|
return new DAOFactory(em);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public GrpcServerProperties grpcServerProperties(@Value("${grpc.port}") int port) {
|
||||||
|
return new GrpcServerProperties(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Profile("grpc")
|
||||||
|
public SpringGrpcServerRunner springGrpcServerRunner() {
|
||||||
|
return new SpringGrpcServerRunner();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public LocalEntityManagerFactoryBean entityManagerFactoryBean() {
|
||||||
|
LocalEntityManagerFactoryBean bean = new LocalEntityManagerFactoryBean();
|
||||||
|
bean.setPersistenceUnitName(Constants.JPA_PERSISTENCE_UNIT);
|
||||||
|
// fixes collection proxy problem when using jersey
|
||||||
|
bean.getJpaPropertyMap().put("hibernate.enable_lazy_load_no_trans", true);
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PlatformTransactionManager transactionManager(LocalEntityManagerFactoryBean entityManagerFactoryBean) {
|
||||||
|
JpaTransactionManager transactionManager = new JpaTransactionManager();
|
||||||
|
transactionManager.setPersistenceUnitName(Constants.JPA_PERSISTENCE_UNIT);
|
||||||
|
transactionManager.setEntityManagerFactory(entityManagerFactoryBean.getObject());
|
||||||
|
return transactionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Profile("testdata")
|
||||||
|
public TestData testData() {
|
||||||
|
return new TestData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Profile("testdata")
|
||||||
|
public TestDataInserter testDataInserter(TestData testData, IModelFactory modelFactory, PlatformTransactionManager transactionManager) {
|
||||||
|
return new TestDataInserter(testData, modelFactory, transactionManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Profile("testdata")
|
||||||
|
public AuthServiceDataInjector dataInjector(TestDataInserter testDataInserter) {
|
||||||
|
return new AuthServiceDataInjector(em, testDataInserter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure data is in the database before the {@link ICachingAuthenticationService} is initialized.
|
||||||
|
*/
|
||||||
|
public static class AuthServiceDataInjector implements BeanPostProcessor {
|
||||||
|
private boolean dataInjected = false;
|
||||||
|
|
||||||
|
private EntityManager em;
|
||||||
|
private TestDataInserter testDataInserter;
|
||||||
|
|
||||||
|
public AuthServiceDataInjector(EntityManager em, TestDataInserter testDataInserter) {
|
||||||
|
this.em = em;
|
||||||
|
this.testDataInserter = testDataInserter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||||
|
if (!dataInjected && (bean instanceof ICachingAuthenticationService)) {
|
||||||
|
testDataInserter.insertTestData(em);
|
||||||
|
dataInjected = true;
|
||||||
|
}
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package dst.ass2.service.auth;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
|
||||||
|
import dst.ass2.service.auth.grpc.IGrpcServerRunner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class loads the {@link IGrpcServerRunner} from the application context and runs it after the application starts.
|
||||||
|
*/
|
||||||
|
public class SpringGrpcServerRunner implements CommandLineRunner, ApplicationContextAware {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SpringGrpcServerRunner.class);
|
||||||
|
|
||||||
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||||
|
this.applicationContext = applicationContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(String... args) throws Exception {
|
||||||
|
LOG.info("Getting instance of GrpcServerRunner");
|
||||||
|
IGrpcServerRunner bean = applicationContext.getBean(IGrpcServerRunner.class);
|
||||||
|
LOG.info("Starting IGrpcServerRunner instance {}", bean);
|
||||||
|
bean.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
package dst.ass2.service.auth;
|
||||||
|
|
||||||
|
import dst.ass1.jpa.model.IModelFactory;
|
||||||
|
import dst.ass1.jpa.tests.TestData;
|
||||||
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
|
||||||
|
public class TestDataInserter {
|
||||||
|
|
||||||
|
private PlatformTransactionManager transactionManager;
|
||||||
|
private IModelFactory modelFactory;
|
||||||
|
private TestData testData;
|
||||||
|
|
||||||
|
public TestDataInserter(TestData testData, IModelFactory modelFactory, PlatformTransactionManager transactionManager) {
|
||||||
|
this.testData = testData;
|
||||||
|
this.modelFactory = modelFactory;
|
||||||
|
this.transactionManager = transactionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertTestData(EntityManager em) {
|
||||||
|
TransactionTemplate tx = new TransactionTemplate(transactionManager);
|
||||||
|
tx.execute(status -> {
|
||||||
|
testData.insert(modelFactory, em);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,143 @@
|
|||||||
|
package dst.ass2.service.auth.tests;
|
||||||
|
|
||||||
|
import dst.ass1.jpa.model.IModelFactory;
|
||||||
|
import dst.ass1.jpa.model.IPreferences;
|
||||||
|
import dst.ass1.jpa.model.IRider;
|
||||||
|
import dst.ass1.jpa.tests.TestData;
|
||||||
|
import dst.ass2.service.api.auth.AuthenticationException;
|
||||||
|
import dst.ass2.service.api.auth.NoSuchUserException;
|
||||||
|
import dst.ass2.service.auth.AuthenticationServiceApplication;
|
||||||
|
import dst.ass2.service.auth.ICachingAuthenticationService;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.PersistenceContext;
|
||||||
|
import javax.transaction.Transactional;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest(classes = AuthenticationServiceApplication.class)
|
||||||
|
@Transactional
|
||||||
|
@ActiveProfiles("testdata")
|
||||||
|
public class AuthenticationServiceTest implements ApplicationContextAware {
|
||||||
|
|
||||||
|
@PersistenceContext
|
||||||
|
private EntityManager em;
|
||||||
|
|
||||||
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
private IModelFactory modelFactory;
|
||||||
|
private ICachingAuthenticationService authenticationService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
|
||||||
|
applicationContext = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
modelFactory = applicationContext.getBean(IModelFactory.class);
|
||||||
|
authenticationService = applicationContext.getBean(ICachingAuthenticationService.class);
|
||||||
|
|
||||||
|
// reload the data before each test
|
||||||
|
authenticationService.loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticate_existingUser_createsTokenCorrectly() throws Exception {
|
||||||
|
String token = authenticationService.authenticate(TestData.RIDER_1_EMAIL, TestData.RIDER_1_PW);
|
||||||
|
assertNotNull(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticate_existingUserNotInCache_createsTokenCorrectly() throws Exception {
|
||||||
|
IRider p = modelFactory.createRider();
|
||||||
|
|
||||||
|
p.setEmail("non-cached@example.com");
|
||||||
|
try {
|
||||||
|
p.setPassword(MessageDigest.getInstance("SHA1").digest("somepw".getBytes()));
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
p.setName("non-cached");
|
||||||
|
p.setAccountNo("accountno");
|
||||||
|
p.setBankCode("bankcode");
|
||||||
|
p.setTel("+43158801");
|
||||||
|
IPreferences prefs = modelFactory.createPreferences();
|
||||||
|
p.setPreferences(prefs);
|
||||||
|
|
||||||
|
em.persist(prefs);
|
||||||
|
em.persist(p);
|
||||||
|
|
||||||
|
String token = authenticationService.authenticate(p.getEmail(), "somepw");
|
||||||
|
assertNotNull(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = NoSuchUserException.class)
|
||||||
|
public void authenticate_invalidUser_throwsException() throws Exception {
|
||||||
|
authenticationService.authenticate("nonexisting@example.com", "foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = AuthenticationException.class)
|
||||||
|
public void authenticate_invalidPassword_throwsException() throws Exception {
|
||||||
|
authenticationService.authenticate(TestData.RIDER_1_EMAIL, "foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void changePassword_existingUser_passwordChanged() throws Exception {
|
||||||
|
authenticationService.changePassword(TestData.RIDER_1_EMAIL, "newPwd");
|
||||||
|
assertNotNull(authenticationService.authenticate(TestData.RIDER_1_EMAIL, "newPwd"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = NoSuchUserException.class)
|
||||||
|
public void changePassword_nonExistingUser_throwsException() throws Exception {
|
||||||
|
authenticationService.changePassword("nonexisting@example.com", "foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getUser_existingToken_returnsUser() throws Exception {
|
||||||
|
String token = authenticationService.authenticate(TestData.RIDER_1_EMAIL, TestData.RIDER_1_PW);
|
||||||
|
assertEquals(TestData.RIDER_1_EMAIL, authenticationService.getUser(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getUser_nonExistingToken_returnsNull() throws Exception {
|
||||||
|
assertNull(authenticationService.getUser("invalidToken"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isValid_existingToken_returnsTrue() throws Exception {
|
||||||
|
String token = authenticationService.authenticate(TestData.RIDER_1_EMAIL, TestData.RIDER_1_PW);
|
||||||
|
assertTrue(authenticationService.isValid(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isValid_nonExistingToken_returnsFalse() throws Exception {
|
||||||
|
assertFalse(authenticationService.isValid("invalidToken"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void invalidate_validToken_tokenInvalidatedReturnsTrue() throws Exception {
|
||||||
|
String token = authenticationService.authenticate(TestData.RIDER_1_EMAIL, TestData.RIDER_1_PW);
|
||||||
|
assertTrue(authenticationService.invalidate(token));
|
||||||
|
assertFalse(authenticationService.isValid(token));
|
||||||
|
assertNull(authenticationService.getUser(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void invalidate_invalidToken_returnsFalse() throws Exception {
|
||||||
|
assertFalse(authenticationService.invalidate("invalidToken"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package dst.ass2.service.auth.tests;
|
||||||
|
|
||||||
|
import dst.ass2.service.auth.AuthenticationServiceApplication;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
@ActiveProfiles({"testdata", "grpc"})
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest(classes = AuthenticationServiceApplication.class)
|
||||||
|
public class GrpcServerRunnerTest {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(GrpcServerRunnerTest.class);
|
||||||
|
|
||||||
|
@Value("${grpc.port}")
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void canConnectToGrpcSocketAfterApplicationInitialization() throws Exception {
|
||||||
|
int n = 4;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
LOG.info("Tyring to connect to TCP socket on localhost:{}", port);
|
||||||
|
try (Socket socket = new Socket("localhost", port)) {
|
||||||
|
return;
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (n == 0) {
|
||||||
|
throw new AssertionError("Expected gRPC server to run on port " + port, e);
|
||||||
|
} else {
|
||||||
|
Thread.sleep(250);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n--;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
grpc.port=50051
|
||||||
53
ass2-service/facade/pom.xml
Normal file
53
ass2-service/facade/pom.xml
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?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-service-facade</artifactId>
|
||||||
|
|
||||||
|
<name>DST :: Assignment 2 :: Service :: Facade</name>
|
||||||
|
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||||
|
<artifactId>ass2-service-api</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||||
|
<artifactId>ass2-service-auth-client</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish.jersey.core</groupId>
|
||||||
|
<artifactId>jersey-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish.jersey.ext</groupId>
|
||||||
|
<artifactId>jersey-proxy-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-jersey</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package dst.ass2.service.facade;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class ServiceFacadeApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(ServiceFacadeApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
package dst.ass2.service.facade;
|
||||||
|
|
||||||
|
import dst.ass2.service.api.auth.AuthenticationException;
|
||||||
|
import dst.ass2.service.api.auth.NoSuchUserException;
|
||||||
|
import dst.ass2.service.auth.client.AuthenticationClientProperties;
|
||||||
|
import dst.ass2.service.auth.client.IAuthenticationClient;
|
||||||
|
import dst.ass2.service.auth.client.impl.GrpcAuthenticationClient;
|
||||||
|
import org.glassfish.jersey.server.ResourceConfig;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.SpringBootConfiguration;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
@SpringBootConfiguration
|
||||||
|
public class ServiceFacadeApplicationConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ResourceConfig jerseyConfig() {
|
||||||
|
return new ResourceConfig()
|
||||||
|
.packages("dst.ass2.service.facade");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public URI tripServiceURI(@Value("${tripservice.uri}") URI target) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthenticationClientProperties authenticationClientProperties(
|
||||||
|
@Value("${auth.host}") String host,
|
||||||
|
@Value("${auth.port}") int port) {
|
||||||
|
return new AuthenticationClientProperties(host, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Profile("!AuthenticationResourceTest")
|
||||||
|
// only use this when we're not running individual tests
|
||||||
|
public IAuthenticationClient grpcAuthenticationClient(AuthenticationClientProperties authenticationClientProperties) {
|
||||||
|
return new GrpcAuthenticationClient(authenticationClientProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Profile("AuthenticationResourceTest")
|
||||||
|
public IAuthenticationClient mockAuthenticationClient() {
|
||||||
|
return new MockAuthenticationClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MockAuthenticationClient implements IAuthenticationClient {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(MockAuthenticationClient.class);
|
||||||
|
|
||||||
|
public static String TOKEN = "123e4567-e89b-12d3-a456-426655440000";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String authenticate(String email, String password) throws NoSuchUserException, AuthenticationException {
|
||||||
|
LOG.info("Calling MockAuthenticationClient with {}, {}", email, password);
|
||||||
|
|
||||||
|
if (email.equals("junit@example.com")) {
|
||||||
|
if (password.equals("junit")) {
|
||||||
|
return TOKEN;
|
||||||
|
}
|
||||||
|
throw new AuthenticationException();
|
||||||
|
}
|
||||||
|
throw new NoSuchUserException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTokenValid(String t) {
|
||||||
|
return TOKEN.equals(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,97 @@
|
|||||||
|
package dst.ass2.service.facade.test;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.allOf;
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.hamcrest.CoreMatchers.not;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.web.server.LocalServerPort;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.http.client.BufferingClientHttpRequestFactory;
|
||||||
|
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.client.HttpClientErrorException;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import dst.ass2.service.facade.ServiceFacadeApplication;
|
||||||
|
import dst.ass2.service.facade.ServiceFacadeApplicationConfig;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest(classes = ServiceFacadeApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
|
@ActiveProfiles("AuthenticationResourceTest")
|
||||||
|
public class AuthenticationResourceTest {
|
||||||
|
|
||||||
|
@LocalServerPort
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
private RestTemplate restTemplate;
|
||||||
|
private HttpHeaders headers;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
headers = new HttpHeaders();
|
||||||
|
restTemplate = new RestTemplate();
|
||||||
|
|
||||||
|
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
|
||||||
|
BufferingClientHttpRequestFactory bufferingClientHttpRequestFactory = new BufferingClientHttpRequestFactory(requestFactory);
|
||||||
|
requestFactory.setOutputStreaming(false);
|
||||||
|
restTemplate.setRequestFactory(bufferingClientHttpRequestFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticate_withValidUser_returnsOkAndToken() throws Exception {
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
String url = url("/auth/authenticate");
|
||||||
|
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
|
||||||
|
body.add("email", "junit@example.com");
|
||||||
|
body.add("password", "junit");
|
||||||
|
|
||||||
|
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||||
|
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||||
|
assertNotNull(response.getBody());
|
||||||
|
assertThat(response.getBody(), is(ServiceFacadeApplicationConfig.MockAuthenticationClient.TOKEN));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticate_withInvalidUser_returnsAppropriateCode() throws Exception {
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
String url = url("/auth/authenticate");
|
||||||
|
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
|
||||||
|
body.add("email", "nonexisting@example.com");
|
||||||
|
body.add("password", "wrong");
|
||||||
|
|
||||||
|
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||||
|
HttpStatus status;
|
||||||
|
|
||||||
|
try {
|
||||||
|
status = restTemplate.postForEntity(url, request, String.class).getStatusCode();
|
||||||
|
} catch (HttpClientErrorException e) {
|
||||||
|
status = e.getStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat("Return an appropriate error code", status, allOf(
|
||||||
|
not(HttpStatus.OK),
|
||||||
|
not(HttpStatus.NOT_FOUND),
|
||||||
|
not(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String url(String uri) {
|
||||||
|
return "http://localhost:" + port + uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
server.port=8090
|
||||||
|
|
||||||
|
# this is the gRPC host the client tries to connect to
|
||||||
|
auth.host=localhost
|
||||||
|
auth.port=50051
|
||||||
|
|
||||||
|
# this is the root of the remote resource that the facade accesses via its client
|
||||||
|
tripservice.uri=http://localhost:8091/
|
||||||
14
ass2-service/facade/src/test/resources/logback.xml
Normal file
14
ass2-service/facade/src/test/resources/logback.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<configuration>
|
||||||
|
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} - %highlight(%5p) [%12.12thread] %cyan(%-40.40logger{39}): %m%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="${log.level:-INFO}">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
62
ass2-service/trip/pom.xml
Normal file
62
ass2-service/trip/pom.xml
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?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-service-trip</artifactId>
|
||||||
|
|
||||||
|
<name>DST :: Assignment 2 :: Service :: Trip</name>
|
||||||
|
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||||
|
<artifactId>ass1-jpa</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||||
|
<artifactId>ass2-service-api</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>at.ac.tuwien.infosys.dst</groupId>
|
||||||
|
<artifactId>ass1-jpa</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<type>test-jar</type>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-orm</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-jersey</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
package dst.ass2.service.trip;
|
||||||
|
|
||||||
|
import dst.ass1.jpa.dao.IDAOFactory;
|
||||||
|
import dst.ass1.jpa.dao.IDriverDAO;
|
||||||
|
import dst.ass1.jpa.model.IDriver;
|
||||||
|
import dst.ass2.service.api.match.IMatchingService;
|
||||||
|
import dst.ass2.service.api.trip.*;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.annotation.ManagedBean;
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.transaction.Transactional;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@ManagedBean
|
||||||
|
@Transactional
|
||||||
|
public class MatchingService implements IMatchingService {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ITripService tripService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private IDAOFactory daoFactory;
|
||||||
|
|
||||||
|
private IDriverDAO driverDAO;
|
||||||
|
|
||||||
|
private int driverIndex;
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(MatchingService.class);
|
||||||
|
private List<IDriver> all;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void setup() {
|
||||||
|
driverDAO = daoFactory.createDriverDAO();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MoneyDTO calculateFare(TripDTO trip) throws InvalidTripException {
|
||||||
|
LOG.info("Calculate fare for trip: {}", trip.getId());
|
||||||
|
BigDecimal value = Math.random() > 0.5 ? BigDecimal.ONE : BigDecimal.TEN;
|
||||||
|
MoneyDTO moneyDTO = new MoneyDTO();
|
||||||
|
moneyDTO.setCurrency("EUR");
|
||||||
|
moneyDTO.setValue(value);
|
||||||
|
return moneyDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void queueTripForMatching(Long tripId) {
|
||||||
|
if (all == null) {
|
||||||
|
all = driverDAO.findAll();
|
||||||
|
driverIndex = all.size() - 2;
|
||||||
|
}
|
||||||
|
TripDTO trip = tripService.find(tripId);
|
||||||
|
try {
|
||||||
|
LOG.info("Queue trip {} for matching", tripId);
|
||||||
|
IDriver driver = all.get(driverIndex++);
|
||||||
|
if (driverIndex >= all.size()) {
|
||||||
|
driverIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
MatchDTO matchDTO = new MatchDTO();
|
||||||
|
matchDTO.setDriverId(driver.getId());
|
||||||
|
matchDTO.setVehicleId(driver.getVehicle().getId());
|
||||||
|
matchDTO.setFare(trip.getFare());
|
||||||
|
tripService.match(tripId, matchDTO);
|
||||||
|
} catch (EntityNotFoundException | DriverNotAvailableException e) {
|
||||||
|
//LOG.error("Error during matching", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package dst.ass2.service.trip;
|
||||||
|
|
||||||
|
import dst.ass1.jpa.model.IModelFactory;
|
||||||
|
import dst.ass1.jpa.tests.TestData;
|
||||||
|
import org.springframework.boot.SpringBootConfiguration;
|
||||||
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.PersistenceContext;
|
||||||
|
|
||||||
|
@SpringBootConfiguration
|
||||||
|
@Profile("testdata")
|
||||||
|
public class TestDataConfig implements ApplicationListener<ApplicationReadyEvent> {
|
||||||
|
|
||||||
|
@PersistenceContext
|
||||||
|
private EntityManager em;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TestData testData() {
|
||||||
|
return new TestData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TestDataInserter testDataInserter(TestData testData, IModelFactory modelFactory, PlatformTransactionManager transactionManager) {
|
||||||
|
return new TestDataInserter(testData, modelFactory, transactionManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(ApplicationReadyEvent event) {
|
||||||
|
event.getApplicationContext()
|
||||||
|
.getBean(TestDataInserter.class)
|
||||||
|
.insertTestData(em);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
package dst.ass2.service.trip;
|
||||||
|
|
||||||
|
import dst.ass1.jpa.model.IModelFactory;
|
||||||
|
import dst.ass1.jpa.tests.TestData;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
|
||||||
|
public class TestDataInserter {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(TestDataInserter.class);
|
||||||
|
|
||||||
|
private PlatformTransactionManager transactionManager;
|
||||||
|
private IModelFactory modelFactory;
|
||||||
|
private TestData testData;
|
||||||
|
|
||||||
|
public TestDataInserter(TestData testData, IModelFactory modelFactory, PlatformTransactionManager transactionManager) {
|
||||||
|
this.testData = testData;
|
||||||
|
this.modelFactory = modelFactory;
|
||||||
|
this.transactionManager = transactionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertTestData(EntityManager em) {
|
||||||
|
LOG.info("Inserting test data...");
|
||||||
|
TransactionTemplate tx = new TransactionTemplate(transactionManager);
|
||||||
|
tx.execute(status -> {
|
||||||
|
testData.insert(modelFactory, em);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
package dst.ass2.service.trip;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableTransactionManagement
|
||||||
|
public class TripApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(TripApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
package dst.ass2.service.trip;
|
||||||
|
|
||||||
|
import dst.ass1.jpa.dao.IDAOFactory;
|
||||||
|
import dst.ass1.jpa.dao.impl.DAOFactory;
|
||||||
|
import dst.ass1.jpa.model.IModelFactory;
|
||||||
|
import dst.ass1.jpa.model.impl.ModelFactory;
|
||||||
|
import dst.ass1.jpa.util.Constants;
|
||||||
|
import org.glassfish.jersey.server.ResourceConfig;
|
||||||
|
import org.springframework.boot.SpringBootConfiguration;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||||
|
import org.springframework.orm.jpa.LocalEntityManagerFactoryBean;
|
||||||
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.PersistenceContext;
|
||||||
|
|
||||||
|
@SpringBootConfiguration
|
||||||
|
public class TripApplicationConfig {
|
||||||
|
|
||||||
|
@PersistenceContext
|
||||||
|
private EntityManager em;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ResourceConfig jerseyConfig() {
|
||||||
|
return new ResourceConfig()
|
||||||
|
.packages("dst.ass2.service.trip");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public IModelFactory modelFactory() {
|
||||||
|
return new ModelFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public IDAOFactory daoFactory() {
|
||||||
|
return new DAOFactory(em);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public LocalEntityManagerFactoryBean entityManagerFactoryBean() {
|
||||||
|
LocalEntityManagerFactoryBean bean = new LocalEntityManagerFactoryBean();
|
||||||
|
bean.setPersistenceUnitName(Constants.JPA_PERSISTENCE_UNIT);
|
||||||
|
// fixes collection proxy problem when using jersey
|
||||||
|
bean.getJpaPropertyMap().put("hibernate.enable_lazy_load_no_trans", true);
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PlatformTransactionManager transactionManager(LocalEntityManagerFactoryBean entityManagerFactoryBean) {
|
||||||
|
JpaTransactionManager transactionManager = new JpaTransactionManager();
|
||||||
|
transactionManager.setPersistenceUnitName(Constants.JPA_PERSISTENCE_UNIT);
|
||||||
|
transactionManager.setEntityManagerFactory(entityManagerFactoryBean.getObject());
|
||||||
|
return transactionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,616 @@
|
|||||||
|
package dst.ass2.service.trip.tests;
|
||||||
|
|
||||||
|
import dst.ass1.jpa.tests.TestData;
|
||||||
|
import dst.ass2.service.api.match.IMatchingService;
|
||||||
|
import dst.ass2.service.api.trip.*;
|
||||||
|
import dst.ass2.service.trip.TripApplication;
|
||||||
|
import org.hamcrest.CoreMatchers;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||||
|
import org.springframework.boot.web.server.LocalServerPort;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.http.*;
|
||||||
|
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.allOf;
|
||||||
|
import static org.hamcrest.CoreMatchers.not;
|
||||||
|
import static org.hamcrest.core.Is.is;
|
||||||
|
import static org.hamcrest.Matchers.hasItem;
|
||||||
|
import static org.hamcrest.core.IsNull.notNullValue;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest(classes = TripApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
|
@ActiveProfiles("testdata")
|
||||||
|
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||||
|
public class TripServiceResourceTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TestData testData;
|
||||||
|
|
||||||
|
@LocalServerPort
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private IMatchingService matchingService;
|
||||||
|
|
||||||
|
private TestRestTemplate restTemplate;
|
||||||
|
private HttpHeaders headers;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RestTemplate restTemplate() {
|
||||||
|
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
|
||||||
|
return new RestTemplate(requestFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
this.headers = new HttpHeaders();
|
||||||
|
this.restTemplate = new TestRestTemplate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String url(String uri) {
|
||||||
|
return "http://localhost:" + port + uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createTrip_withWrongHttpMethod_returnsError() {
|
||||||
|
String url = url("/trips");
|
||||||
|
MultiValueMap<String, String> map = getCreateMap(2134L, 2222L, 33L);
|
||||||
|
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class, map);
|
||||||
|
|
||||||
|
assertThat("Response was: " + response, response.getStatusCode().series(), is(HttpStatus.Series.CLIENT_ERROR));
|
||||||
|
assertThat(response.getStatusCode(), not(HttpStatus.NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createTrip_withUnknownRider_returnsNotFoundError() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
String url = url("/trips");
|
||||||
|
MultiValueMap<String, String> body = getCreateMap(3333L, testData.location1Id, testData.location2Id);
|
||||||
|
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||||
|
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
|
||||||
|
|
||||||
|
assertThat("Response was: " + response, response.getStatusCode(), is(HttpStatus.NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createTrip_withValidKeys_returnsTripId() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
String url = url("/trips");
|
||||||
|
MultiValueMap<String, String> body = getCreateMap(testData.rider1Id, testData.location1Id, testData.location2Id);
|
||||||
|
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||||
|
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||||
|
assertThat(response.getBody(), notNullValue());
|
||||||
|
|
||||||
|
try {
|
||||||
|
Long.parseLong(response.getBody());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new AssertionError("Response body of " + url + " should be a Long value (the trip ID)", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createTrip_withInvalidTrip_andThenGetTrip_hasNoFare() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenThrow(new InvalidTripException());
|
||||||
|
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
String url = url("/trips");
|
||||||
|
MultiValueMap<String, String> body = getCreateMap(testData.rider1Id, testData.location1Id, testData.location2Id);
|
||||||
|
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||||
|
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||||
|
assertThat(response.getBody(), notNullValue());
|
||||||
|
|
||||||
|
try {
|
||||||
|
long id = Long.parseLong(response.getBody());
|
||||||
|
ResponseEntity<TripDTO> trip = getTrip(id);
|
||||||
|
assertThat(trip.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||||
|
assertNull(trip.getBody().getFare());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new AssertionError("Response body of " + url + " should be a Long value (the trip ID)", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findTrip_withUnknownKey_returnsNotFoundError() {
|
||||||
|
String url = url("/trips/" + (Long) 1338L);
|
||||||
|
ResponseEntity<String> trip = restTemplate.getForEntity(url, String.class);
|
||||||
|
assertThat(trip.getStatusCode(), is(HttpStatus.NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findTrip_onCreatedTrip_returnsJsonEntity() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
Long pickup = testData.location5Id;
|
||||||
|
Long rider = testData.rider4Id;
|
||||||
|
Long destination = testData.location1Id;
|
||||||
|
Long trip = testData.trip6Id;
|
||||||
|
String url = url("/trips/" + trip);
|
||||||
|
|
||||||
|
ResponseEntity<TripDTO> response = restTemplate.getForEntity(url, TripDTO.class);
|
||||||
|
TripDTO tripDTO = response.getBody();
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||||
|
assertNotNull(tripDTO);
|
||||||
|
assertEquals(trip, tripDTO.getId());
|
||||||
|
assertEquals(destination, tripDTO.getDestinationId());
|
||||||
|
assertEquals(pickup, tripDTO.getPickupId());
|
||||||
|
assertEquals(rider, tripDTO.getRiderId());
|
||||||
|
assertEquals(3, tripDTO.getStops().size());
|
||||||
|
assertEquals(getTen(), tripDTO.getFare());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addStop_onCreatedTrip_returnsOkAndCalculatedFare() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
String url = url("/trips/" + testData.trip6Id + "/stops");
|
||||||
|
MultiValueMap<String, String> body = body("locationId", testData.location5Id);
|
||||||
|
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||||
|
ResponseEntity<MoneyDTO> response = restTemplate.postForEntity(url, request, MoneyDTO.class);
|
||||||
|
MoneyDTO moneyDTO = response.getBody();
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||||
|
assertNotNull(moneyDTO);
|
||||||
|
assertEquals(getTen(), moneyDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addStop_andThenGetTrip_containsStopInList() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
|
||||||
|
String url = url("/trips/" + testData.trip6Id + "/stops");
|
||||||
|
MultiValueMap<String, String> body = body("locationId", testData.location5Id);
|
||||||
|
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||||
|
ResponseEntity<MoneyDTO> response = restTemplate.postForEntity(url, request, MoneyDTO.class);
|
||||||
|
assertThat(response.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||||
|
|
||||||
|
ResponseEntity<TripDTO> tripResponse = getTrip(testData.trip6Id);
|
||||||
|
assertThat(tripResponse.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||||
|
assertThat(tripResponse.getBody().getStops(), hasItem(testData.location5Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addStop_onQueuedTrip_returnsAppropriateError() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
|
||||||
|
String url = url("/trips/" + testData.trip10Id + "/stops");
|
||||||
|
MultiValueMap<String, String> body = body("locationId", testData.location5Id);
|
||||||
|
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||||
|
ResponseEntity<?> response = restTemplate.postForEntity(url, request, String.class);
|
||||||
|
|
||||||
|
assertThat("Make use of an appropriate HTTP status code", response.getStatusCode(), allOf(
|
||||||
|
CoreMatchers.is(not(HttpStatus.OK)),
|
||||||
|
CoreMatchers.is(not(HttpStatus.NOT_FOUND)),
|
||||||
|
CoreMatchers.is(not(HttpStatus.INTERNAL_SERVER_ERROR))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addStop_withNonExistingLocation_returnsNotFoundError() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
|
||||||
|
Long trip = testData.trip6Id;
|
||||||
|
|
||||||
|
String url = url("/trips/" + trip + "/stops");
|
||||||
|
MultiValueMap<String, String> body = body("locationId", 1337L);
|
||||||
|
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||||
|
ResponseEntity<?> response = restTemplate.postForEntity(url, request, String.class);
|
||||||
|
assertThat(response.getStatusCode(), is(HttpStatus.NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addStop_withLocationAlreadyInStopList_returnsAppropriateError() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
|
||||||
|
Long trip = testData.trip6Id;
|
||||||
|
|
||||||
|
String url = url("/trips/" + trip + "/stops");
|
||||||
|
MultiValueMap<String, String> body = body("locationId", testData.location2Id);
|
||||||
|
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||||
|
ResponseEntity<?> response = restTemplate.postForEntity(url, request, String.class);
|
||||||
|
assertThat("Make use of an appropriate HTTP status code", response.getStatusCode(), allOf(
|
||||||
|
CoreMatchers.is(not(HttpStatus.OK)),
|
||||||
|
CoreMatchers.is(not(HttpStatus.NOT_FOUND)),
|
||||||
|
CoreMatchers.is(not(HttpStatus.INTERNAL_SERVER_ERROR))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addStop_withInvalidTrip_returnsOk() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenThrow(new InvalidTripException());
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
|
||||||
|
Long trip = testData.trip6Id;
|
||||||
|
|
||||||
|
String url = url("/trips/" + trip + "/stops");
|
||||||
|
MultiValueMap<String, String> body = body("locationId", testData.location5Id);
|
||||||
|
HttpEntity<?> request = new HttpEntity<>(body, headers);
|
||||||
|
ResponseEntity<?> response = restTemplate.postForEntity(url, request, String.class);
|
||||||
|
assertThat(response.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeStop_onCreatedTrip_returnOk() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
Long trip = testData.trip6Id;
|
||||||
|
Long location = testData.location2Id;
|
||||||
|
String url = url("/trips/" + trip + "/stops/" + location);
|
||||||
|
|
||||||
|
ResponseEntity<String> exchange = restTemplate.exchange(
|
||||||
|
url,
|
||||||
|
HttpMethod.DELETE,
|
||||||
|
null,
|
||||||
|
(Class<String>) null,
|
||||||
|
new HashMap<String, String>()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(exchange.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeStop_andThenGetTrip_doesntContainStopInList() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
|
||||||
|
Long trip = testData.trip6Id;
|
||||||
|
Long location = testData.location2Id;
|
||||||
|
String url = url("/trips/" + trip + "/stops/" + location);
|
||||||
|
|
||||||
|
restTemplate.delete(url, new HashMap<>());
|
||||||
|
ResponseEntity<TripDTO> response = getTrip(trip);
|
||||||
|
assertNotNull(response);
|
||||||
|
List<Long> ids = response.getBody().getStops();
|
||||||
|
assertFalse(ids.contains(location));
|
||||||
|
assertTrue(ids.contains(testData.location3Id));
|
||||||
|
assertTrue(ids.contains(testData.location4Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeStop_onQueuedTrip_returnsAppropriateError() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
Long trip = testData.trip10Id;
|
||||||
|
Long location = testData.location4Id;
|
||||||
|
String url = url("/trips/" + trip + "/stops/" + location);
|
||||||
|
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(
|
||||||
|
url,
|
||||||
|
HttpMethod.DELETE,
|
||||||
|
null,
|
||||||
|
(Class<String>) null,
|
||||||
|
new HashMap<String, String>()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat("Make use of an appropriate HTTP status code", response.getStatusCode(), allOf(
|
||||||
|
CoreMatchers.is(not(HttpStatus.OK)),
|
||||||
|
CoreMatchers.is(not(HttpStatus.NOT_FOUND)),
|
||||||
|
CoreMatchers.is(not(HttpStatus.INTERNAL_SERVER_ERROR))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeStop_withNonExistingLocation_returnsNotFoundError() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
Long trip = testData.trip6Id;
|
||||||
|
Long location = 1234L;
|
||||||
|
String url = url("/trips/" + trip + "/stops/" + location);
|
||||||
|
|
||||||
|
ResponseEntity<String> exchange = restTemplate.exchange(
|
||||||
|
url,
|
||||||
|
HttpMethod.DELETE,
|
||||||
|
null,
|
||||||
|
(Class<String>) null,
|
||||||
|
new HashMap<String, String>()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(exchange.getStatusCode(), is(HttpStatus.NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeStop_WithLocationNotInStopsList_returnsAppropriateError() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
Long trip = testData.trip6Id;
|
||||||
|
Long location = testData.location5Id;
|
||||||
|
String url = url("/trips/" + trip + "/stops/" + location);
|
||||||
|
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(
|
||||||
|
url,
|
||||||
|
HttpMethod.DELETE,
|
||||||
|
null,
|
||||||
|
(Class<String>) null,
|
||||||
|
new HashMap<String, String>()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat("Make use of an appropriate HTTP status code", response.getStatusCode(), allOf(
|
||||||
|
CoreMatchers.is(not(HttpStatus.OK)),
|
||||||
|
CoreMatchers.is(not(HttpStatus.NOT_FOUND)),
|
||||||
|
CoreMatchers.is(not(HttpStatus.INTERNAL_SERVER_ERROR))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeStop_withInvalidTrip_returnsOk() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenThrow(new InvalidTripException());
|
||||||
|
Long trip = testData.trip6Id;
|
||||||
|
Long location = testData.location2Id;
|
||||||
|
String url = url("/trips/" + trip + "/stops/" + location);
|
||||||
|
|
||||||
|
ResponseEntity<String> exchange = restTemplate.exchange(
|
||||||
|
url,
|
||||||
|
HttpMethod.DELETE,
|
||||||
|
null,
|
||||||
|
(Class<String>) null,
|
||||||
|
new HashMap<String, String>()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(exchange.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void confirmTrip_withKnownTrip_shouldReturnSuccessful() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
|
||||||
|
Long trip = testData.trip6Id;
|
||||||
|
String url = url("/trips/" + trip + "/confirm");
|
||||||
|
|
||||||
|
ResponseEntity<String> exchange = restTemplate.exchange(
|
||||||
|
url,
|
||||||
|
HttpMethod.PATCH,
|
||||||
|
null,
|
||||||
|
(Class<String>) null,
|
||||||
|
new HashMap<String, String>()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(exchange.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void confirmTrip_withUnknownTrip_shouldReturnNotFoundError() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
|
||||||
|
Long trip = 1244L;
|
||||||
|
String url = url("/trips/" + trip + "/confirm");
|
||||||
|
|
||||||
|
ResponseEntity<String> exchange = restTemplate.exchange(
|
||||||
|
url,
|
||||||
|
HttpMethod.PATCH,
|
||||||
|
null,
|
||||||
|
(Class<String>) null,
|
||||||
|
new HashMap<String, String>()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(exchange.getStatusCode(), is(HttpStatus.NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void confirmTrip_withInvalidTrip_shouldReturnAppropriateError() throws InvalidTripException {
|
||||||
|
when(matchingService.calculateFare(any())).thenThrow(new InvalidTripException());
|
||||||
|
|
||||||
|
Long trip = testData.trip6Id;
|
||||||
|
String url = url("/trips/" + trip + "/confirm");
|
||||||
|
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(
|
||||||
|
url,
|
||||||
|
HttpMethod.PATCH,
|
||||||
|
null,
|
||||||
|
(Class<String>) null,
|
||||||
|
new HashMap<String, String>()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat("Make use of an appropriate HTTP status code", response.getStatusCode(), allOf(
|
||||||
|
CoreMatchers.is(not(HttpStatus.OK)),
|
||||||
|
CoreMatchers.is(not(HttpStatus.NOT_FOUND)),
|
||||||
|
CoreMatchers.is(not(HttpStatus.INTERNAL_SERVER_ERROR))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteTrip_withKnownTrip_shouldReturnSuccessful() {
|
||||||
|
Long trip = testData.trip6Id;
|
||||||
|
String url = url("/trips/" + trip);
|
||||||
|
|
||||||
|
ResponseEntity<String> exchange = restTemplate.exchange(
|
||||||
|
url,
|
||||||
|
HttpMethod.DELETE,
|
||||||
|
null,
|
||||||
|
(Class<String>) null,
|
||||||
|
new HashMap<String, String>()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(exchange.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteTrip_withUnknownTrip_shouldReturnNotFoundError() {
|
||||||
|
Long trip = 1245L;
|
||||||
|
String url = url("/trips/" + trip);
|
||||||
|
|
||||||
|
ResponseEntity<String> exchange = restTemplate.exchange(
|
||||||
|
url,
|
||||||
|
HttpMethod.DELETE,
|
||||||
|
null,
|
||||||
|
(Class<String>) null,
|
||||||
|
new HashMap<String, String>()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(exchange.getStatusCode(), is(HttpStatus.NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cancelTrip_withKnownKey_shouldReturnSuccessful() {
|
||||||
|
Long trip = testData.trip6Id;
|
||||||
|
String url = url("/trips/" + trip + "/cancel");
|
||||||
|
|
||||||
|
ResponseEntity<String> exchange = restTemplate.exchange(
|
||||||
|
url,
|
||||||
|
HttpMethod.PATCH,
|
||||||
|
null,
|
||||||
|
(Class<String>) null,
|
||||||
|
new HashMap<String, String>()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(exchange.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cancelTrip_withUnknownKey_shouldReturnNotFoundError() {
|
||||||
|
Long trip = 12545L;
|
||||||
|
String url = url("/trips/" + trip + "/cancel");
|
||||||
|
|
||||||
|
ResponseEntity<String> exchange = restTemplate.exchange(
|
||||||
|
url,
|
||||||
|
HttpMethod.PATCH,
|
||||||
|
null,
|
||||||
|
(Class<String>) null,
|
||||||
|
new HashMap<String, String>()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(exchange.getStatusCode(), is(HttpStatus.NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void completeTrip_withKnownTrip_shouldReturnSuccessful() {
|
||||||
|
Long trip = testData.trip9Id;
|
||||||
|
TripInfoDTO tripInfoDTO = new TripInfoDTO();
|
||||||
|
tripInfoDTO.setCompleted(new Date());
|
||||||
|
tripInfoDTO.setDistance(100.0);
|
||||||
|
tripInfoDTO.setFare(getTen());
|
||||||
|
String url = url("/trips/" + trip + "/complete");
|
||||||
|
|
||||||
|
ResponseEntity<String> exchange = restTemplate.postForEntity(
|
||||||
|
url,
|
||||||
|
tripInfoDTO,
|
||||||
|
String.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(exchange.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void completeTrip_withUnknownTrip_shouldReturnNotFoundError() {
|
||||||
|
Long trip = 12545L;
|
||||||
|
TripInfoDTO tripInfoDTO = new TripInfoDTO();
|
||||||
|
tripInfoDTO.setCompleted(new Date());
|
||||||
|
tripInfoDTO.setDistance(100.0);
|
||||||
|
tripInfoDTO.setFare(getTen());
|
||||||
|
String url = url("/trips/" + trip + "/complete");
|
||||||
|
|
||||||
|
ResponseEntity<String> exchange = restTemplate.postForEntity(
|
||||||
|
url,
|
||||||
|
tripInfoDTO,
|
||||||
|
String.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(exchange.getStatusCode(), is(HttpStatus.NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void matchTrip_withKnownTrip_shouldReturnSuccessful() {
|
||||||
|
Long trip = testData.trip10Id;
|
||||||
|
Long driver = testData.driver4Id + 1L;
|
||||||
|
Long vehicle = testData.vehicle1Id;
|
||||||
|
String url = url("/trips/" + trip + "/match");
|
||||||
|
|
||||||
|
MatchDTO matchDTO = new MatchDTO();
|
||||||
|
matchDTO.setVehicleId(vehicle);
|
||||||
|
matchDTO.setDriverId(driver);
|
||||||
|
matchDTO.setFare(getTen());
|
||||||
|
|
||||||
|
ResponseEntity<String> exchange = restTemplate.postForEntity(
|
||||||
|
url,
|
||||||
|
matchDTO,
|
||||||
|
String.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(exchange.getStatusCode().series(), is(HttpStatus.Series.SUCCESSFUL));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void matchTrip_withUnavailableDriver_shouldReturnAppropriateError() {
|
||||||
|
Long trip = testData.trip10Id;
|
||||||
|
Long driver = testData.driver4Id;
|
||||||
|
Long vehicle = testData.vehicle1Id;
|
||||||
|
String url = url("/trips/" + trip + "/match");
|
||||||
|
|
||||||
|
MatchDTO matchDTO = new MatchDTO();
|
||||||
|
matchDTO.setVehicleId(vehicle);
|
||||||
|
matchDTO.setDriverId(driver);
|
||||||
|
matchDTO.setFare(getTen());
|
||||||
|
|
||||||
|
ResponseEntity<String> response = restTemplate.postForEntity(
|
||||||
|
url,
|
||||||
|
matchDTO,
|
||||||
|
String.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat("Make use of an appropriate HTTP status code", response.getStatusCode(), allOf(
|
||||||
|
CoreMatchers.is(not(HttpStatus.OK)),
|
||||||
|
CoreMatchers.is(not(HttpStatus.NOT_FOUND)),
|
||||||
|
CoreMatchers.is(not(HttpStatus.INTERNAL_SERVER_ERROR))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private MultiValueMap<String, String> getCreateMap(Long riderId, Long pickupId, Long destinationId) {
|
||||||
|
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
|
||||||
|
map.add("riderId", riderId + "");
|
||||||
|
map.add("pickupId", pickupId + "");
|
||||||
|
map.add("destinationId", destinationId + "");
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private MoneyDTO getTen() {
|
||||||
|
MoneyDTO moneyDTO = new MoneyDTO();
|
||||||
|
moneyDTO.setCurrency("EUR");
|
||||||
|
moneyDTO.setValue(BigDecimal.TEN);
|
||||||
|
return moneyDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MultiValueMap<String, String> body(String key, Object value) {
|
||||||
|
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
|
||||||
|
map.add(key, String.valueOf(value));
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseEntity<TripDTO> getTrip(Long id) {
|
||||||
|
String url = url("/trips/" + id);
|
||||||
|
return restTemplate.getForEntity(url, TripDTO.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,464 @@
|
|||||||
|
package dst.ass2.service.trip.tests;
|
||||||
|
|
||||||
|
import dst.ass1.jpa.dao.IDAOFactory;
|
||||||
|
import dst.ass1.jpa.dao.ITripDAO;
|
||||||
|
import dst.ass1.jpa.model.*;
|
||||||
|
import dst.ass1.jpa.tests.TestData;
|
||||||
|
import dst.ass2.service.api.match.IMatchingService;
|
||||||
|
import dst.ass2.service.api.trip.*;
|
||||||
|
import dst.ass2.service.trip.TripApplication;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest(classes = TripApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
|
@ActiveProfiles("testdata")
|
||||||
|
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||||
|
public class TripServiceTest implements ApplicationContextAware {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(TripServiceTest.class);
|
||||||
|
|
||||||
|
private ApplicationContext ctx;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private IMatchingService matchingService;
|
||||||
|
|
||||||
|
private ITripService tripService;
|
||||||
|
private IDAOFactory daoFactory;
|
||||||
|
private TestData testData;
|
||||||
|
private ITripDAO tripDAO;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
|
||||||
|
this.ctx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
LOG.info("Test resolving beans from application context");
|
||||||
|
daoFactory = ctx.getBean(IDAOFactory.class);
|
||||||
|
tripService = ctx.getBean(ITripService.class);
|
||||||
|
testData = ctx.getBean(TestData.class);
|
||||||
|
tripDAO = daoFactory.createTripDAO();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateWithValidArguments_persistsTrip_and_returnsTripDTO() throws Exception {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getOne());
|
||||||
|
|
||||||
|
TripDTO tripDTO = tripService.create(testData.rider1Id, testData.location1Id, testData.location2Id);
|
||||||
|
|
||||||
|
verify(matchingService, times(1)).calculateFare(any());
|
||||||
|
|
||||||
|
assertNotNull(tripDTO);
|
||||||
|
assertNotNull(tripDTO.getId());
|
||||||
|
assertNotNull(tripDTO.getFare());
|
||||||
|
|
||||||
|
assertEquals(testData.rider1Id, tripDTO.getRiderId());
|
||||||
|
assertEquals(testData.location1Id, tripDTO.getPickupId());
|
||||||
|
assertEquals(testData.location2Id, tripDTO.getDestinationId());
|
||||||
|
assertEquals(getOne(), tripDTO.getFare());
|
||||||
|
|
||||||
|
ITrip trip = tripDAO.findById(tripDTO.getId());
|
||||||
|
assertNotNull(trip);
|
||||||
|
|
||||||
|
assertEquals(testData.location1Id, trip.getPickup().getId());
|
||||||
|
assertEquals(testData.location2Id, trip.getDestination().getId());
|
||||||
|
assertEquals(testData.rider1Id, trip.getRider().getId());
|
||||||
|
assertEquals(TripState.CREATED, trip.getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test(expected = EntityNotFoundException.class)
|
||||||
|
public void testCreateWithInvalidRider_throwsException() throws Exception {
|
||||||
|
tripService.create(1337L, testData.location1Id, testData.location2Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = EntityNotFoundException.class)
|
||||||
|
public void testCreateWithInvalidPickup_throwsException() throws Exception {
|
||||||
|
tripService.create(testData.rider1Id, 1444L, testData.location2Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = EntityNotFoundException.class)
|
||||||
|
public void testCreateWithInvalidDestination_throwsException() throws Exception {
|
||||||
|
tripService.create(testData.rider1Id, testData.location1Id, 1337L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateWithInvalidTrip_setsFareToNull() throws Exception {
|
||||||
|
when(matchingService.calculateFare(any())).thenThrow(new InvalidTripException());
|
||||||
|
|
||||||
|
TripDTO tripDTO = tripService.create(testData.rider1Id, testData.location1Id, testData.location2Id);
|
||||||
|
assertNotNull(tripDTO);
|
||||||
|
assertNotNull(tripDTO.getId());
|
||||||
|
|
||||||
|
ITrip trip = tripDAO.findById(tripDTO.getId());
|
||||||
|
assertNotNull(trip);
|
||||||
|
assertNull(tripDTO.getFare());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConfirmWithValidTrip_isQueued() throws Exception {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
|
||||||
|
tripService.confirm(testData.trip6Id);
|
||||||
|
ITrip confirmed = tripDAO.findById(testData.trip6Id);
|
||||||
|
assertEquals(TripState.QUEUED, confirmed.getState());
|
||||||
|
verify(matchingService, times(1)).queueTripForMatching(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = EntityNotFoundException.class)
|
||||||
|
public void testConfirmWithUnknownTrip_throwsException() throws Exception {
|
||||||
|
tripService.confirm(1337L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
public void testConfirmQueuedTrip_throwsException() throws Exception {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getOne());
|
||||||
|
tripService.confirm(testData.trip10Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidTripException.class)
|
||||||
|
public void testConfirmWithInvalidTrip_throwsException() throws Exception {
|
||||||
|
when(matchingService.calculateFare(any())).thenThrow(new InvalidTripException());
|
||||||
|
tripService.confirm(testData.trip6Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMatch_matchCreated_stateMatched() throws Exception {
|
||||||
|
Long tripId = testData.trip10Id;
|
||||||
|
|
||||||
|
MatchDTO matchDTO = new MatchDTO();
|
||||||
|
matchDTO.setFare(getOne());
|
||||||
|
matchDTO.setDriverId(testData.driver4Id + 1);
|
||||||
|
matchDTO.setVehicleId(testData.vehicle1Id);
|
||||||
|
|
||||||
|
tripService.match(tripId, matchDTO);
|
||||||
|
|
||||||
|
ITrip updated = tripDAO.findById(tripId);
|
||||||
|
assertNotNull(updated);
|
||||||
|
assertEquals(TripState.MATCHED, updated.getState());
|
||||||
|
|
||||||
|
IMatch match = updated.getMatch();
|
||||||
|
assertNotNull(match);
|
||||||
|
assertEquals(matchDTO.getDriverId(), match.getDriver().getId());
|
||||||
|
assertEquals(matchDTO.getVehicleId(), match.getVehicle().getId());
|
||||||
|
|
||||||
|
IMoney fare = match.getFare();
|
||||||
|
assertEquals(getOne().getCurrency(), fare.getCurrency());
|
||||||
|
assertEquals(getOne().getValue().compareTo(fare.getValue()), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMatchWithUnknownTrip_throwsException_and_requeueTrip() throws Exception {
|
||||||
|
long tripId = 1337L;
|
||||||
|
|
||||||
|
MatchDTO matchDTO = new MatchDTO();
|
||||||
|
matchDTO.setFare(getOne());
|
||||||
|
matchDTO.setDriverId(testData.driver4Id + 1);
|
||||||
|
matchDTO.setVehicleId(testData.vehicle1Id);
|
||||||
|
|
||||||
|
try {
|
||||||
|
tripService.match(tripId, matchDTO);
|
||||||
|
} catch (EntityNotFoundException ex) {
|
||||||
|
verify(matchingService, times(1)).queueTripForMatching(any());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMatch_driverAlreadyAssigned_throwsException_and_requeueTrip() throws Exception {
|
||||||
|
Long tripId = testData.trip10Id;
|
||||||
|
|
||||||
|
MatchDTO matchDTO = new MatchDTO();
|
||||||
|
matchDTO.setFare(getTen());
|
||||||
|
matchDTO.setDriverId(testData.driver4Id);
|
||||||
|
matchDTO.setVehicleId(testData.vehicle1Id);
|
||||||
|
|
||||||
|
try {
|
||||||
|
tripService.match(tripId, matchDTO);
|
||||||
|
} catch (DriverNotAvailableException ex) {
|
||||||
|
verify(matchingService, times(1)).queueTripForMatching(any());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompleteWithValidTripInfo_shouldPersistTripInfo_and_setTripCompleted() throws Exception {
|
||||||
|
Long tripId = testData.trip9Id;
|
||||||
|
TripInfoDTO tripInfoDTO = new TripInfoDTO();
|
||||||
|
tripInfoDTO.setCompleted(new Date());
|
||||||
|
tripInfoDTO.setFare(getTen());
|
||||||
|
tripInfoDTO.setDistance(2.0);
|
||||||
|
|
||||||
|
tripService.complete(tripId, tripInfoDTO);
|
||||||
|
|
||||||
|
ITrip updated = tripDAO.findById(tripId);
|
||||||
|
assertNotNull(updated);
|
||||||
|
assertEquals(TripState.COMPLETED, updated.getState());
|
||||||
|
|
||||||
|
ITripInfo tripInfo = updated.getTripInfo();
|
||||||
|
assertNotNull(tripInfo);
|
||||||
|
|
||||||
|
assertEquals(tripInfoDTO.getCompleted(), tripInfo.getCompleted());
|
||||||
|
assertEquals(2.0, tripInfo.getDistance(), 0);
|
||||||
|
assertEquals(getTen().getCurrency(), tripInfo.getTotal().getCurrency());
|
||||||
|
assertEquals(0, getTen().getValue().compareTo(tripInfo.getTotal().getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = EntityNotFoundException.class)
|
||||||
|
public void testCompleteWithUnknownTrip_throwsException() throws Exception {
|
||||||
|
long tripId = 1337L;
|
||||||
|
TripInfoDTO tripInfoDTO = new TripInfoDTO();
|
||||||
|
tripInfoDTO.setCompleted(new Date());
|
||||||
|
tripInfoDTO.setFare(getTen());
|
||||||
|
tripInfoDTO.setDistance(2.0);
|
||||||
|
|
||||||
|
tripService.complete(tripId, tripInfoDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCancelWithValidTrip_shouldCancelTrip() throws Exception {
|
||||||
|
tripService.cancel(testData.trip6Id);
|
||||||
|
|
||||||
|
ITrip updated = tripDAO.findById(testData.trip6Id);
|
||||||
|
assertNotNull(updated);
|
||||||
|
assertEquals(TripState.CANCELLED, updated.getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = EntityNotFoundException.class)
|
||||||
|
public void testCancelWithUnkownTrip_throwsException() throws Exception {
|
||||||
|
tripService.cancel(1337L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddStop_shouldReturnTrue() throws Exception {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
|
||||||
|
TripDTO tripDTO = getTrip6DTO();
|
||||||
|
tripDTO.setFare(getTen());
|
||||||
|
|
||||||
|
boolean added = tripService.addStop(tripDTO, testData.location1Id);
|
||||||
|
assertTrue(added);
|
||||||
|
assertEquals(getTen(), tripDTO.getFare());
|
||||||
|
verify(matchingService, times(1)).calculateFare(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = EntityNotFoundException.class)
|
||||||
|
public void testAddStopUnknownLocation_throwsException() throws Exception {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
|
||||||
|
TripDTO tripDTO = getTrip6DTO();
|
||||||
|
tripDTO.setFare(getTen());
|
||||||
|
|
||||||
|
tripService.addStop(tripDTO, 1344L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
public void testAddStopInvalidTripState_throwsException() throws Exception {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
|
||||||
|
ITrip trip = tripDAO.findById(testData.trip1Id);
|
||||||
|
TripDTO tripDTO = new TripDTO();
|
||||||
|
tripDTO.setId(trip.getId());
|
||||||
|
tripDTO.setPickupId(trip.getPickup().getId());
|
||||||
|
tripDTO.setDestinationId(trip.getDestination().getId());
|
||||||
|
tripDTO.setRiderId(trip.getRider().getId());
|
||||||
|
tripDTO.setStops(trip.getStops().stream().map(ILocation::getId).collect(Collectors.toList()));
|
||||||
|
tripDTO.setFare(getTen());
|
||||||
|
|
||||||
|
tripService.addStop(tripDTO, testData.location2Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddStopInvalidTrip_setsFareToNull() throws Exception {
|
||||||
|
when(matchingService.calculateFare(any())).thenThrow(new InvalidTripException());
|
||||||
|
|
||||||
|
TripDTO tripDTO = getTrip6DTO();
|
||||||
|
tripDTO.setFare(getOne());
|
||||||
|
|
||||||
|
boolean added = tripService.addStop(tripDTO, testData.location1Id);
|
||||||
|
assertTrue(added);
|
||||||
|
assertNull(tripDTO.getFare());
|
||||||
|
assertEquals(4, tripDTO.getStops().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddStop_shouldReturnFalse() throws Exception {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
|
||||||
|
TripDTO tripDTO = getTrip6DTO();
|
||||||
|
tripDTO.setFare(getOne());
|
||||||
|
|
||||||
|
boolean added = tripService.addStop(tripDTO, testData.location2Id);
|
||||||
|
assertFalse(added);
|
||||||
|
|
||||||
|
//fare should not be updated if the location hasn't been added
|
||||||
|
assertEquals(getOne(), tripDTO.getFare());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveStop_shouldReturnTrue() throws Exception {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getOne());
|
||||||
|
|
||||||
|
TripDTO tripDTO = getTrip6DTO();
|
||||||
|
tripDTO.setFare(getTen());
|
||||||
|
|
||||||
|
boolean removed = tripService.removeStop(tripDTO, testData.location2Id);
|
||||||
|
assertTrue(removed);
|
||||||
|
assertEquals(getOne(), tripDTO.getFare());
|
||||||
|
assertEquals(2, tripDTO.getStops().size());
|
||||||
|
ITrip updated = tripDAO.findById(testData.trip6Id);
|
||||||
|
assertEquals(2, updated.getStops().size());
|
||||||
|
verify(matchingService, times(1)).calculateFare(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
private MoneyDTO getZero() {
|
||||||
|
MoneyDTO moneyDTO = new MoneyDTO();
|
||||||
|
moneyDTO.setCurrency("EUR");
|
||||||
|
moneyDTO.setValue(BigDecimal.ZERO);
|
||||||
|
return moneyDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveStop_shouldReturnFalse() throws Exception {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getZero());
|
||||||
|
TripDTO tripDTO = getTrip6DTO();
|
||||||
|
tripDTO.setFare(getOne());
|
||||||
|
|
||||||
|
boolean removed = tripService.removeStop(tripDTO, testData.location1Id);
|
||||||
|
assertFalse(removed);
|
||||||
|
|
||||||
|
//fare should not be updated if the location hasn't been removed
|
||||||
|
assertEquals(getOne(), tripDTO.getFare());
|
||||||
|
|
||||||
|
assertEquals(3, tripDTO.getStops().size());
|
||||||
|
ITrip updated = tripDAO.findById(testData.trip6Id);
|
||||||
|
assertEquals(3, updated.getStops().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = EntityNotFoundException.class)
|
||||||
|
public void testRemoveStopUnknownLocation_throwsException() throws Exception {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getOne());
|
||||||
|
|
||||||
|
TripDTO tripDTO = getTrip6DTO();
|
||||||
|
tripDTO.setFare(getTen());
|
||||||
|
|
||||||
|
tripService.removeStop(tripDTO, 1344L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
public void testRemoveStopInvalidTripState_throwsException() throws Exception {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getOne());
|
||||||
|
|
||||||
|
ITrip trip = tripDAO.findById(testData.trip10Id);
|
||||||
|
TripDTO tripDTO = new TripDTO();
|
||||||
|
tripDTO.setId(trip.getId());
|
||||||
|
tripDTO.setPickupId(trip.getPickup().getId());
|
||||||
|
tripDTO.setDestinationId(trip.getDestination().getId());
|
||||||
|
tripDTO.setRiderId(trip.getRider().getId());
|
||||||
|
List<Long> stopIds = trip.getStops().stream().map(ILocation::getId).collect(Collectors.toList());
|
||||||
|
tripDTO.setStops(stopIds);
|
||||||
|
tripDTO.setFare(getTen());
|
||||||
|
|
||||||
|
tripService.removeStop(tripDTO, testData.location4Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveStopInvalidTrip_setsFareToNull() throws Exception {
|
||||||
|
when(matchingService.calculateFare(any())).thenThrow(new InvalidTripException());
|
||||||
|
|
||||||
|
TripDTO tripDTO = getTrip6DTO();
|
||||||
|
tripDTO.setFare(getOne());
|
||||||
|
|
||||||
|
boolean removed = tripService.removeStop(tripDTO, testData.location4Id);
|
||||||
|
assertTrue(removed);
|
||||||
|
assertNull(tripDTO.getFare());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteValidTrip_shouldSucceed() throws Exception {
|
||||||
|
tripService.delete(testData.trip6Id);
|
||||||
|
ITrip deleted = tripDAO.findById(testData.trip6Id);
|
||||||
|
assertNull(deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = EntityNotFoundException.class)
|
||||||
|
public void testDeleteUnknownTrip_throwsException() throws Exception {
|
||||||
|
tripService.delete(1111L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindTrip_shouldSucceed() throws Exception {
|
||||||
|
when(matchingService.calculateFare(any())).thenReturn(getTen());
|
||||||
|
|
||||||
|
TripDTO tripDTO = tripService.find(testData.trip9Id);
|
||||||
|
assertNotNull(tripDTO);
|
||||||
|
assertEquals(testData.trip9Id, tripDTO.getId());
|
||||||
|
assertEquals(testData.rider1Id, tripDTO.getRiderId());
|
||||||
|
assertEquals(testData.location2Id, tripDTO.getPickupId());
|
||||||
|
assertEquals(testData.location5Id, tripDTO.getDestinationId());
|
||||||
|
assertEquals(getTen(), tripDTO.getFare());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindTrip_shouldReturnNull() {
|
||||||
|
TripDTO tripDTO = tripService.find(1337L);
|
||||||
|
assertNull(tripDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MoneyDTO getOne() {
|
||||||
|
MoneyDTO moneyDTO = new MoneyDTO();
|
||||||
|
moneyDTO.setCurrency("EUR");
|
||||||
|
moneyDTO.setValue(BigDecimal.ONE);
|
||||||
|
return moneyDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MoneyDTO getTen() {
|
||||||
|
MoneyDTO moneyDTO = new MoneyDTO();
|
||||||
|
moneyDTO.setValue(BigDecimal.TEN);
|
||||||
|
moneyDTO.setCurrency("EUR");
|
||||||
|
return moneyDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private TripDTO getTrip6DTO() {
|
||||||
|
TripDTO tripDTO = new TripDTO();
|
||||||
|
tripDTO.setId(testData.trip6Id);
|
||||||
|
tripDTO.setPickupId(testData.location5Id);
|
||||||
|
tripDTO.setDestinationId(testData.location1Id);
|
||||||
|
tripDTO.setRiderId(testData.rider4Id);
|
||||||
|
LinkedList<Long> longs = new LinkedList<>();
|
||||||
|
longs.add(testData.location2Id);
|
||||||
|
longs.add(testData.location3Id);
|
||||||
|
longs.add(testData.location4Id);
|
||||||
|
tripDTO.setStops(longs);
|
||||||
|
return tripDTO;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
server.port=8091
|
||||||
208
pom.xml
208
pom.xml
@ -43,6 +43,13 @@
|
|||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>${maven-surefire-plugin.version}</version>
|
<version>${maven-surefire-plugin.version}</version>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven.surefire</groupId>
|
||||||
|
<artifactId>surefire-junit4</artifactId>
|
||||||
|
<version>${maven-surefire-plugin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
<configuration>
|
<configuration>
|
||||||
<failIfNoTests>false</failIfNoTests>
|
<failIfNoTests>false</failIfNoTests>
|
||||||
<runOrder>alphabetical</runOrder>
|
<runOrder>alphabetical</runOrder>
|
||||||
@ -255,6 +262,131 @@
|
|||||||
<artifactId>javassist</artifactId>
|
<artifactId>javassist</artifactId>
|
||||||
<version>${javassist.version}</version>
|
<version>${javassist.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- assignment 2+ -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>${commons-io.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.aspectj</groupId>
|
||||||
|
<artifactId>aspectjrt</artifactId>
|
||||||
|
<version>${aspectj.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.aspectj</groupId>
|
||||||
|
<artifactId>aspectjweaver</artifactId>
|
||||||
|
<version>${aspectj.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-aop</artifactId>
|
||||||
|
<version>${spring.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>${commons-lang3.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-all</artifactId>
|
||||||
|
<version>${grpc.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-netty</artifactId>
|
||||||
|
<version>${grpc.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-protobuf</artifactId>
|
||||||
|
<version>${grpc.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-stub</artifactId>
|
||||||
|
<version>${grpc.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-testing</artifactId>
|
||||||
|
<version>${grpc.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-orm</artifactId>
|
||||||
|
<version>${spring.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-context</artifactId>
|
||||||
|
<version>${spring.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-jersey</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<!-- a transitive dependency org.glassfish.hk2:spring-bridge imports spring-context 4.3.16 -->
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-context</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish.jersey.core</groupId>
|
||||||
|
<artifactId>jersey-client</artifactId>
|
||||||
|
<version>${jersey.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<!-- explicated for consistency with other jersey dependencies -->
|
||||||
|
<groupId>org.glassfish.jersey.core</groupId>
|
||||||
|
<artifactId>jersey-server</artifactId>
|
||||||
|
<version>${jersey.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish.jersey.ext</groupId>
|
||||||
|
<artifactId>jersey-proxy-client</artifactId>
|
||||||
|
<version>${jersey.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-core</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-annotations</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>${httpclient.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
@ -266,6 +398,13 @@
|
|||||||
<module>ass1-jpa</module>
|
<module>ass1-jpa</module>
|
||||||
<module>ass1-doc</module>
|
<module>ass1-doc</module>
|
||||||
<module>ass1-kv</module>
|
<module>ass1-kv</module>
|
||||||
|
<module>ass2-service/api</module>
|
||||||
|
<module>ass2-service/auth-client</module>
|
||||||
|
<module>ass2-service/auth</module>
|
||||||
|
<module>ass2-service/trip</module>
|
||||||
|
<module>ass2-service/facade</module>
|
||||||
|
<module>ass2-aop</module>
|
||||||
|
<module>ass2-ioc</module>
|
||||||
</modules>
|
</modules>
|
||||||
</profile>
|
</profile>
|
||||||
|
|
||||||
@ -290,6 +429,65 @@
|
|||||||
</modules>
|
</modules>
|
||||||
</profile>
|
</profile>
|
||||||
|
|
||||||
|
<profile>
|
||||||
|
<id>ass2-service</id>
|
||||||
|
<modules>
|
||||||
|
<module>ass1-jpa</module>
|
||||||
|
<module>ass2-service/api</module>
|
||||||
|
<module>ass2-service/auth-client</module>
|
||||||
|
<module>ass2-service/auth</module>
|
||||||
|
<module>ass2-service/trip</module>
|
||||||
|
<module>ass2-service/facade</module>
|
||||||
|
</modules>
|
||||||
|
</profile>
|
||||||
|
|
||||||
|
<profile>
|
||||||
|
<id>ass2-aop</id>
|
||||||
|
<modules>
|
||||||
|
<module>ass2-aop</module>
|
||||||
|
</modules>
|
||||||
|
</profile>
|
||||||
|
|
||||||
|
<profile>
|
||||||
|
<id>ass2-ioc</id>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>2.6</version>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifestEntries>
|
||||||
|
<Premain-Class>dst.ass2.ioc.lock.LockingInjectorAgent</Premain-Class>
|
||||||
|
</manifestEntries>
|
||||||
|
</archive>
|
||||||
|
<finalName>dst-ioc-agent</finalName>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
<phase>test-compile</phase>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<argLine>-javaagent:"${project.build.directory}/dst-ioc-agent.jar"</argLine>
|
||||||
|
<includes>
|
||||||
|
<include>dst/ass2/ioc/**/*.java</include>
|
||||||
|
</includes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
<modules>
|
||||||
|
<module>ass2-ioc</module>
|
||||||
|
</modules>
|
||||||
|
</profile>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</profiles>
|
</profiles>
|
||||||
@ -321,6 +519,16 @@
|
|||||||
<h2.version>1.4.200</h2.version>
|
<h2.version>1.4.200</h2.version>
|
||||||
<mongodb.version>3.12.8</mongodb.version>
|
<mongodb.version>3.12.8</mongodb.version>
|
||||||
<flapdoodle.version>3.0.0</flapdoodle.version>
|
<flapdoodle.version>3.0.0</flapdoodle.version>
|
||||||
|
<!-- assignment 2 -->
|
||||||
|
<aspectj.version>1.9.6</aspectj.version>
|
||||||
|
<spring.version>5.3.4</spring.version>
|
||||||
|
<spring-boot.version>2.4.3</spring-boot.version>
|
||||||
|
<jersey.version>2.32</jersey.version>
|
||||||
|
<commons-io.version>2.8.0</commons-io.version>
|
||||||
|
<grpc.version>1.35.0</grpc.version>
|
||||||
|
<protobuf-maven-plugin.version>0.6.1</protobuf-maven-plugin.version>
|
||||||
|
<os-maven-plugin.version>1.7.0</os-maven-plugin.version>
|
||||||
|
<httpclient.version>4.5.13</httpclient.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user