/** * Copyright (C) 2012 Vincenzo Pirrone * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package com.kdcloud.lib.client; import java.io.IOException; import java.util.LinkedList; import java.util.List; import java.util.Queue; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.restlet.data.ChallengeScheme; import org.restlet.data.MediaType; import org.restlet.data.Status; import org.restlet.ext.xml.DomRepresentation; import org.restlet.representation.Representation; import org.restlet.resource.ClientResource; import org.restlet.resource.ResourceException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import weka.core.Instances; import com.kdcloud.lib.domain.DataSpecification; import com.kdcloud.lib.domain.Index; import com.kdcloud.lib.domain.ModalitySpecification; import com.kdcloud.lib.domain.ServerAction; import com.kdcloud.lib.domain.ServerParameter; import com.kdcloud.lib.domain.ServerParameter.ReferenceType; import com.kdcloud.lib.rest.api.IndexResource; import com.kdcloud.lib.rest.ext.InstancesRepresentation; public abstract class BaseClient implements Runnable { // the server url String baseUri; // the executing modality ModalitySpecification modality; // used for restlet requests ClientResource resource; // stores each (xml) output of any server request Document executionLog; // utility factories DocumentBuilder documentBuilder; XPath xpath; // queue of actions to execute Queue<ServerAction> queue; // used sto stop execution private boolean canRun; // whether or not repeating repeatable actions private boolean repeatAllowed; // the current executing action private ServerAction currentAction; // the modality output private Instances output; public abstract void log(String message, Throwable thrown); public abstract void log(String message); /** * this method is called when a request (PUT) requires data to send * * @return the instances to send */ public abstract Instances getData(); /** * returns a selected string between the ones in choices * * @param parameterName * a string specifing the semantic meaning of the choices * @param choices * the possibles choice alternatives * @return the choosen alternative */ public abstract String handleChoice(String parameterName, String[] choices); /** * this method is called when an xml report is available * * @param view * the xml report to handle */ public abstract void report(Document view); /** * checks if a single action can be repeated * * @return */ public synchronized boolean isRepeatAllowed() { return repeatAllowed; } public synchronized void setRepeatAllowed(boolean repeatAllowed) { this.repeatAllowed = repeatAllowed; } public BaseClient(String url) throws ParserConfigurationException { this(url, null); } public BaseClient(String url, ModalitySpecification modality) throws ParserConfigurationException { super(); this.baseUri = url; this.modality = modality; // initialize xml stuff DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); this.documentBuilder = dbf.newDocumentBuilder(); XPathFactory xPathfactory = XPathFactory.newInstance(); this.xpath = xPathfactory.newXPath(); // creates the client resource this.resource = new ClientResource(url); this.resource.setRequestEntityBuffering(true); // initialize control variables this.canRun = true; this.repeatAllowed = true; } public synchronized void stopModalityExecution() { canRun = false; } public synchronized void startModalityExecution() { canRun = true; } public synchronized boolean canRun() { return canRun; } public synchronized ModalitySpecification getModality() { return modality; } /** * changes client modality, stops the execution of the previous if any * * @param modality */ public synchronized void setModality(ModalitySpecification modality) { this.canRun = false; this.modality = modality; } public void setAccessToken(String token) { log("setting access token"); setAuthentication("oauth", token); } public void setAuthentication(String userId, String password) { resource.setChallengeResponse(ChallengeScheme.HTTP_BASIC, userId, password); } public List<ModalitySpecification> getModalities() { setResourceReference("/modality"); return getModalities(resource); } public static List<ModalitySpecification> getModalities(String url, String accessToken) { ClientResource cr = new ClientResource(url + "/modality"); cr.setChallengeResponse(ChallengeScheme.HTTP_BASIC, "oauth", accessToken); return getModalities(cr); } private static List<ModalitySpecification> getModalities(ClientResource cr) { LinkedList<ModalitySpecification> list = new LinkedList<ModalitySpecification>(); Index modalityIndex = cr.wrap(IndexResource.class).buildIndex(); for (String uri : modalityIndex.getAllReferences()) { cr.setReference(uri); System.out.println(uri); ModalitySpecification spec = cr.get(ModalitySpecification.class); list.add(spec); } return list; } @Override public void run() { try { executeModality(); } catch (Exception e) { log(e.getMessage(), e); } } /** * executed the predefined modality * * @throws IOException * @throws InterruptedException */ public void executeModality() throws IOException, InterruptedException { startModalityExecution(); this.executionLog = this.documentBuilder.newDocument(); Element rootElement = this.executionLog.createElementNS("dummy", "execution"); this.executionLog.appendChild(rootElement); queue = new LinkedList<ServerAction>(); queue.add(new ServerAction(modality.getInitAction())); queue.add(new ServerAction(modality.getAction())); while (canRun() && !queue.isEmpty()) { currentAction = queue.poll(); currentAction.waitTriggers(); if (repeatAllowed && currentAction.isRepeat()) queue.add(new ServerAction(currentAction)); while (currentAction.hasParameters()) setActionParameter(); try { executeAction(); } catch (ResourceException e) { handleResourceException(resource.getStatus(), e); } } } /** * this method is called when a server request does not terminate correctly * * @param status * the request status * @param e * the exception thrown by the restlet ClientResource */ public abstract void handleResourceException(Status status, ResourceException e); protected void setActionParameter() throws IOException { ServerParameter parameter = currentAction.getParams().get(0); try { log("executing xpath expression: " + parameter.getReferenceExpression()); XPathExpression expr = xpath.compile(parameter .getReferenceExpression()); NodeList result = (NodeList) expr.evaluate(executionLog, XPathConstants.NODESET); log("expression result length: " + result.getLength()); String[] choices = new String[result.getLength()]; for (int i = 0; i < choices.length; i++) { choices[i] = result.item(i).getTextContent(); } handleParameter(parameter, choices); } catch (XPathExpressionException e) { throw new IOException(e.getMessage()); } } private void handleParameter(ServerParameter parameter, String[] values) throws IOException { if (parameter.getReferenceType() == ReferenceType.CHOICE) { if (values.length == 0) throw new IOException("missing parameter"); String value = handleChoice(parameter.getName(), values); log("setting parameter " + parameter.getName() + ":" + value); currentAction.setParameter(parameter, value); } else { Queue<ServerAction> newQueue = new LinkedList<ServerAction>(); for (int i = 0; i < values.length; i++) { ServerAction copy = new ServerAction(currentAction); log("setting parameter " + parameter.getName() + ":" + values[i]); copy.setParameter(parameter, values[i]); newQueue.add(copy); } newQueue.addAll(queue); queue = newQueue; currentAction = queue.poll(); } } protected void setResourceReference(String uri) { String reference = baseUri + uri; log("fetching " + reference); resource.setReference(reference); } /** * retries the execution of the last server action * * @throws IOException * if the requests fails */ public void retryRequest() throws IOException { try { executeAction(); } catch (ResourceException e) { throw new IOException(e.getMessage()); } } /** * executes the current action * * @throws IOException * if the client fails to read the response * @throws ResourceException * if there has been an error during the request */ public void executeAction() throws IOException, ResourceException { setResourceReference(currentAction.getUri()); beforeRequest(); Representation entity = null; switch (currentAction.getMethod()) { case GET: log("executing GET"); entity = resource.get(MediaType.APPLICATION_ALL_XML); break; case PUT: log("executing PUT"); Instances data = getData(); if (!modality.getInputSpecification().matchingSpecification(data)) throw new IOException("input does not match specification"); Representation putRep = currentAction.getPutRepresentation(data); entity = resource.put(putRep); break; case DELETE: log("executing DELETE"); entity = resource.delete(); break; case POST: log("executing POST"); Representation postRep = currentAction.getPostRepresentation(); entity = resource.post(postRep); default: break; } if (!entity.isEmpty()) { handleEntity(entity); } else { log("received entity is empty, doing nothing"); } } /** * this method is called before each server request */ public void beforeRequest() { } /** * handles each response * * @param entity * the response entity * @throws IOException * if the client fails to read the entity */ protected void handleEntity(Representation entity) throws IOException { log("handling entity"); MediaType t = entity.getMediaType(); if (t.equals(MediaType.APPLICATION_XML) || t.equals(MediaType.TEXT_XML)) { Document lastOutput = new DomRepresentation(entity).getDocument(); // DOMImplementation domImpl = lastOutput.getImplementation(); // DOMImplementationLS domImplLS = // (DOMImplementationLS)domImpl.getFeature("LS", "3.0"); // LSSerializer serializer = domImplLS.createLSSerializer(); // System.out.println(serializer.writeToString(lastOutput)); log("storing last output"); Node child = executionLog .adoptNode(lastOutput.getDocumentElement()); executionLog.getDocumentElement().appendChild(child); } else { output = new InstancesRepresentation(entity).getInstances(); DataSpecification outSpec = modality.getOutputSpecification(); if (outSpec != null && !outSpec.matchingSpecification(output)) throw new IOException( "output does not match the specifications"); String view = modality.getOutputSpecification().getView(); if (view != null) report(view); } } private void report(String viewResource) throws IOException { log("generating report"); setResourceReference(viewResource); // Document view = resource.wrap(ViewResource.class).getView(); Representation rep = resource.get(); DomRepresentation domRep = new DomRepresentation(rep); Document view = domRep.getDocument(); XmlReport.mergeWithData(view, output); report(view); } }