/*
* Copyright Ericsson AB 2011-2014. All Rights Reserved.
*
* The contents of this file are subject to the Lesser GNU Public License,
* (the "License"), either version 2.1 of the License, or
* (at your option) any later version.; you may not use this file except in
* compliance with the License. You should have received a copy of the
* License along with this software. If not, it can be
* retrieved online at https://www.gnu.org/licenses/lgpl.html. Moreover
* it could also be requested from Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
* WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
* EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
* OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND,
* EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
* LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE,
* YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
*
* IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
* WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
* REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR
* DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL
* DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY
* (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
* INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE
* OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH
* HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
*/
package com.ericsson.deviceaccess;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Test to implement support for. Sending a request and filling in the result in
* a provided object. write something like
* <p/>
* my actor { on request from client: send request request to request.device on
* response from device send response to client
* <p/>
* <p/>
* action context innehåller 1) device:service:action+arguments 2) sender +
* empty result
* <p/>
* 1. Hitta device.service.action och anropa executeAsync(ac): släpper direkt 2.
* Adaptor utför action och fyller i resultat 2. Adaptor anropar AC.reply(),
* vilken kommer att anropa mekanism given från client att sända resultatet
* <p/>
* <p/>
* }
*/
public class AsyncActions {
public static void main(String[] args) throws InterruptedException {
WarpConnection warpConnection = new WarpConnection(true);
warpConnection.receive("Kalle");
Thread.sleep(30 * 1000);
System.out.println("No aggr");
warpConnection = new WarpConnection(false);
warpConnection.receive("Kalle");
Thread.sleep(30 * 1000);
System.exit(0);
}
static class TestActions implements Actions, ActionContext.ResultRecipient {
private ResultRecipient recipient;
private List<? extends ActionContext> contexts = Arrays.asList(
new TestActionContext("d1", "s1", "banan"),
new TestActionContext("d2", "s2", "apa"),
new TestActionContext("d2", "s1", "orange"),
new TestActionContext("d3", "s1", "mango"));
private Set completed = new HashSet(contexts.size());
private boolean aggregate;
@Override
public void setResultRecipient(ResultRecipient recipient) {
this.recipient = recipient;
for (Iterator iterator = contexts.iterator(); iterator.hasNext();) {
ActionContext actionContext = (ActionContext) iterator.next();
actionContext.setResultRecipient(this);
}
}
@Override
public void aggregateResults(boolean aggregate) {
this.aggregate = aggregate;
}
@Override
public List getActionContexts() {
return contexts;
}
@Override
public void onCompleted(ActionContext ctx) {
System.out.println(ctx + " was completed!");
completed.add(ctx);
if (aggregate) {
if (completed.size() == contexts.size()) {
recipient.onCompleted(contexts);
} else {
ArrayList c = new ArrayList(contexts);
c.removeAll(completed);
System.out.println("Waiting for " + c);
}
} else {
recipient.onCompleted(Arrays.asList(ctx));
}
}
@Override
public String toString() {
return contexts.toString();
}
private static class TestActionContext implements ActionContext {
private String actionName;
private ResultRecipient resultRecipient;
private String serviceName;
private String devId;
private Throwable fail;
TestActionContext(String devId, String serviceName, String actionName) {
this.devId = devId;
this.serviceName = serviceName;
this.actionName = actionName;
}
@Override
public void sendResult() {
resultRecipient.onCompleted(this);
}
@Override
public Result getResult() {
return new Result() {
@Override
public String toString() {
return actionName + " result";
}
};
}
@Override
public String getActionName() {
return actionName;
}
@Override
public String getDeviceId() {
return devId;
}
@Override
public String getServiceName() {
return serviceName;
}
@Override
public Throwable getFailure() {
return fail;
}
@Override
public void setResultRecipient(ResultRecipient resultRecipient) {
this.resultRecipient = resultRecipient;
}
@Override
public void setFailure(Throwable e) {
fail = e;
}
@Override
public String toString() {
return getDeviceId() + ":" + getServiceName() + ":" + getActionName() + ":" + getResult();
}
}
}
static class Executor {
private static final Device DEFAULT_DEVICE = new Device() {
@Override
public Service getService(String serviceName) {
return DEFAULT_SERVICE;
}
}; // returns default service
private static final Service DEFAULT_SERVICE = new Service() {
@Override
public Action getAction(String actionName) {
return DEFAULT_ACTION;
}
};
private static final Action DEFAULT_ACTION = new Action() {
private int seq;
/**
* This method must be implemented by the adaptor. Either it -
* executes several actions in parallel (e.g. UPnP), or - sends them
* to queue for execution one at the time (e.g Z-wave)
*
* @param ctx
*/
@Override
public void executeAsyncWith(final ActionContext ctx) {
// Should set failure info
new Thread("ExecutorThread-" + seq++) {
@Override
public void run() {
try {
System.out.println(this + " executes " + ctx);
ctx.sendResult();
} catch (Throwable e) {
ctx.setFailure(e);
}
}
}.start();
}
};
Map<String, Device> devices = new HashMap<String, Device>() {
{
put("d2", new Device() {
final List<Runnable> jobs = new ArrayList<>();
final Thread runner = new Thread() {
@Override
public void run() {
while (true) {
ArrayList<Runnable> tmpJobs = new ArrayList<>();
synchronized (jobs) {
try {
jobs.wait();
} catch (InterruptedException e) {
continue;
}
tmpJobs.addAll(jobs);
jobs.clear();
}
tmpJobs.stream().forEach((job) -> {
job.run();
});
}
}
};
{
runner.start();
}
@Override
public Service getService(String serviceName) {
return (String actionName) -> (final ActionContext ctx) -> {
synchronized (jobs) {
jobs.add(() -> {
System.out.println("Starting job on " + ctx);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// ignore
}
System.out.println("Completed job on " + ctx);
ctx.sendResult();
});
jobs.notifyAll();
}
};
}
});
}
};
public void execute(Actions actions) {
for (Iterator iterator = actions.getActionContexts().iterator(); iterator.hasNext();) {
final ActionContext actionContext = (ActionContext) iterator.next();
final Action action = getDevice(actionContext.getDeviceId()).
getService(actionContext.getServiceName()).
getAction(actionContext.getActionName());
action.executeAsyncWith(actionContext);
}
}
private Device getDevice(String device) {
if (devices.containsKey(device)) {
return devices.get(device);
}
return DEFAULT_DEVICE;
}
}
static class WarpConnection implements Actions.ResultRecipient {
Executor executor = new Executor();
private boolean aggr;
WarpConnection(boolean aggr) {
this.aggr = aggr;
}
@Override
public void onCompleted(List<? extends ActionContext> completedActions) {
// Make JSON (marshal) ctx Send to warp
System.out.println("Sending " + completedActions + " via warp...");
}
void receive(String data) {
// parse data (unmarshal) into ActionContext
Actions actions = Actions.SerializationSupport.deserialize(data);
actions.aggregateResults(aggr);
actions.setResultRecipient(this);
executor.execute(actions);
}
}
interface Device {
Service getService(String serviceName);
}
interface Service {
Action getAction(String actionName);
}
interface Result {
}
interface Actions {
void setResultRecipient(ResultRecipient recipient);
void aggregateResults(boolean aggregate);
List getActionContexts();
interface ResultRecipient {
void onCompleted(List<? extends ActionContext> completedActions);
}
class SerializationSupport {
public static Actions deserialize(String data) {
return new TestActions();
}
private SerializationSupport() {
}
}
}
interface ActionContext {
void sendResult(); // does recipient.accept(this);
Result getResult();
String getActionName();
String getDeviceId();
String getServiceName();
Throwable getFailure();
void setResultRecipient(ResultRecipient actionResultRecipient);
void setFailure(Throwable e);
interface ResultRecipient {
void onCompleted(ActionContext ctx);
}
}
interface Action {
void executeAsyncWith(ActionContext ctx);
// put ctx into jobQueue
// notify exec thread
// exec thread will take ctx and invoke the action, fill in the result and then invoke ctx.reply()
}
}