/* * Copyright (C) 2014 Intel Corporation * All rights reserved. */ package com.intel.mtwilson.v2.rpc; import com.fasterxml.jackson.databind.ObjectMapper; import com.intel.dcsg.cpg.io.UUID; import com.intel.mtwilson.rpc.v2.model.RpcLocator; import com.intel.mtwilson.rpc.v2.resource.RpcAdapter; import com.intel.mtwilson.rpc.v2.resource.RpcRepository; import java.util.concurrent.ConcurrentLinkedQueue; import com.intel.dcsg.cpg.validation.Fault; import com.intel.mtwilson.launcher.ext.annotations.Background; import com.intel.mtwilson.rpc.v2.model.Rpc; import com.intel.mtwilson.rpc.v2.model.RpcPriv; import com.intel.mtwilson.rpc.v2.model.RpcCollection; import com.intel.mtwilson.rpc.v2.model.RpcFilterCriteria; import com.thoughtworks.xstream.XStream; import java.nio.charset.Charset; import java.util.List; /** * * @author jbuhacoff */ @Background public class RpcInvoker implements Runnable { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RpcInvoker.class); // private static final RpcInvoker instance = new RpcInvoker(); // public static RpcInvoker getInstance() { return instance; } private RpcRepository repository = new RpcRepository(); private ConcurrentLinkedQueue<UUID> queue = new ConcurrentLinkedQueue<>(); // of rpc request uuid's to run private ObjectMapper mapper = new ObjectMapper(); // public void setRepository(RpcRepository repository) { this.repository = repository; } public void add(UUID id) { queue.offer(id); } public void remove(UUID id) { queue.remove(id); } // gets next uuid from queue and invokes it // if you want continous processing of queue, use start() and stop() // instead. @Override public void run() { UUID id = queue.poll(); if( id == null ) { // the queue doesn't have any requests - check the database, add any QUEUE'd tasks to the queue RpcFilterCriteria criteria = new RpcFilterCriteria(); criteria.status = Rpc.Status.QUEUE; RpcCollection results = repository.search(criteria); // the result is "Rpc" class but it will not have inputs or outputs set because the "search" method does not retrieve those from database -- which is fine, we onlyw ant the id's anyway List<Rpc> items = results.getDocuments(); log.debug("Found {} tasks marked QUEUE in database", items.size()); for(Rpc item : items) { queue.offer(item.getId()); } id = queue.poll(); if( id == null ) { log.debug("No tasks in queue and no saved QUEUE tasks in database"); return; } } RpcLocator locator = new RpcLocator(); locator.id = id; RpcPriv rpc = repository.retrieveInput(locator); // retrieve(locator) would only return the status info ; so we have an additional retrieveInput method to also return the input if (rpc == null) { log.error("Cannot retrieve rpc input."); return; } // make sure we have an extension to handle this rpc RpcAdapter adapter = RpcUtil.findRpcForName(rpc.getName()); if( adapter == null ) { log.error("Cannot find RPC extension for {}", rpc.getName()); rpc.setFaults(new Fault[] { new Fault("Unsupported operation") }); rpc.setStatus(Rpc.Status.ERROR); repository.store(rpc); return; } XStream xs = new XStream(); Object taskObject; // parse the request body and deserialize to automaticaly set the task inputs try { taskObject = xs.fromXML(new String(rpc.getInput(), Charset.forName("UTF-8"))); log.debug("Input object: {}", mapper.writeValueAsString(taskObject)); } catch(Exception e) { log.error("Cannot read input: {}", e.getMessage()); rpc.setFaults(new Fault[] { new Fault("Cannot read input") }); rpc.setStatus(Rpc.Status.ERROR); repository.store(rpc); return; } // run try { // assume that the rpc adapter is RunnableRpcAdapter // Runnable runnable = (Runnable)taskObject; // runnable.run(); adapter.setInput(taskObject); adapter.invoke(); // log.debug("After run: {}", mapper.writeValueAsString(taskObject)); log.debug("After run: {}", mapper.writeValueAsString(adapter.getOutput())); } catch(Exception e) { log.error("Error while executing RPC {}", rpc.getName(), e); rpc.setFaults(new Fault[] { new Fault("Execution failed") }); rpc.setStatus(Rpc.Status.ERROR); repository.store(rpc); return; } // format output try { /* javax.ws.rs.core.MultivaluedHashMap jaxrsHeaders = new javax.ws.rs.core.MultivaluedHashMap(); jaxrsHeaders.putAll(headerMap.getMap()); ByteArrayOutputStream out = new ByteArrayOutputStream(); messageBodyWriter.writeTo(taskObject, adapter.getOutputClass(), adapter.getOutputClass(), new Annotation[]{}, outputMediaType, jaxrsHeaders, out); byte[] output = out.toByteArray(); // this will go in database log.debug("Intermediate output: {}", new String(output)); // we can only do this because we know the output is xml format for testing... rpc.setOutput(output); rpc.setOutputContentType(adapter.getContentType()); rpc.setOutputContentClass(adapter.getOutputClass().getName()); */ // rpc.setOutput( xs.toXML(taskObject).getBytes("UTF-8")); rpc.setOutput(xs.toXML(adapter.getOutput()).getBytes("UTF-8")); // the OUTPUT status indicates the task has completed and output is avaialble rpc.setStatus(Rpc.Status.OUTPUT); } catch(Exception e) { log.error("Cannot write output: {}", e.getMessage()); rpc.setFaults(new Fault[] { new Fault("Cannot write output") }); rpc.setStatus(Rpc.Status.ERROR); repository.store(rpc); return; } // Task is done. Now we check the progres -- if the task itself didn't report progress the current/max will be 0/0 , so we change it to 1/1 // but if the task did report progress, then it's max will be non-zero , and in that case we leave it alone. if( rpc.getMax() == null || rpc.getMax().longValue() == 0L ) { rpc.setMax(1L); rpc.setCurrent(1L); } repository.store(rpc); log.debug("RPC processing complete, output stored, status updated to OUTPUT"); } }