package com.delcyon.capo.controller.server;
import java.util.HashMap;
import java.util.logging.Level;
import javax.xml.transform.dom.DOMSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.delcyon.capo.CapoApplication;
import com.delcyon.capo.CapoApplication.Location;
import com.delcyon.capo.Configuration.PREFERENCE;
import com.delcyon.capo.ContextThread;
import com.delcyon.capo.annotations.DefaultDocumentProvider;
import com.delcyon.capo.annotations.DirectoyProvider;
import com.delcyon.capo.controller.elements.GroupElement;
import com.delcyon.capo.exceptions.MissingAttributeException;
import com.delcyon.capo.preferences.Preference;
import com.delcyon.capo.preferences.PreferenceInfo;
import com.delcyon.capo.preferences.PreferenceInfoHelper;
import com.delcyon.capo.preferences.PreferenceProvider;
import com.delcyon.capo.protocol.server.AbstractClientRequestProcessor;
import com.delcyon.capo.protocol.server.ClientRequest;
import com.delcyon.capo.protocol.server.ClientRequestProcessorProvider;
import com.delcyon.capo.protocol.server.ClientRequestXMLProcessor;
import com.delcyon.capo.resourcemanager.ResourceDescriptor;
import com.delcyon.capo.resourcemanager.ResourceDescriptor.Action;
import com.delcyon.capo.resourcemanager.ResourceParameter;
import com.delcyon.capo.resourcemanager.types.FileResourceType;
import com.delcyon.capo.server.CapoServer;
import com.delcyon.capo.xml.XPath;
@PreferenceProvider(preferences=ControllerClientRequestProcessor.Preferences.class)
@DirectoyProvider(preferenceName="CONTROLLER_DIR",preferences=PREFERENCE.class,location=Location.SERVER)
@DefaultDocumentProvider(directoryPreferenceName="CONTROLLER_DIR",preferences=ControllerClientRequestProcessor.Preferences.class,name="default.xml,update.xml,identity.xml,tasks_update.xml",location=Location.SERVER)
@ClientRequestProcessorProvider(name="ControllerRequest")
//@XMLProcessorProvider(documentElementNames={"ControllerRequest"},namespaceURIs={"http://www.delcyon.com/capo-client","http://www.delcyon.com/capo-server"})
public class ControllerClientRequestProcessor extends AbstractClientRequestProcessor
{
public enum Preferences implements Preference
{
@PreferenceInfo(arguments={"file"}, defaultValue="default.xml", description="default controller file, relative to controller dir", longOption="DEFAULT_CONTROLLER_FILE", option="DEFAULT_CONTROLLER_FILE")
DEFAULT_CONTROLLER_FILE,
@PreferenceInfo(arguments={"file"}, defaultValue="identity.xml", description="main identity script file, relative to controller dir", longOption="DEFAULT_IDENTITY_FILE", option="DEFAULT_IDENTITY_FILE")
DEFAULT_IDENTITY_FILE,
@PreferenceInfo(arguments={"file"}, defaultValue="update.xml", description="main update script file, relative to controller dir", longOption="DEFAULT_UPDATE_FILE", option="DEFAULT_UPDATE_FILE")
DEFAULT_UPDATE_FILE,
@PreferenceInfo(arguments={"file"}, defaultValue="tasks_update.xml", description="main tasks update script file, relative to controller dir", longOption="DEFAULT_TASKS_UPDATE_FILE", option="DEFAULT_TASKS_UPDATE_FILE")
DEFAULT_TASKS_UPDATE_FILE;
@Override
public String[] getArguments()
{
return PreferenceInfoHelper.getInfo(this).arguments();
}
@Override
public String getDefaultValue()
{
return PreferenceInfoHelper.getInfo(this).defaultValue();
}
@Override
public String getDescription()
{
return PreferenceInfoHelper.getInfo(this).description();
}
@Override
public String getLongOption()
{
return PreferenceInfoHelper.getInfo(this).longOption();
}
@Override
public String getOption()
{
return PreferenceInfoHelper.getInfo(this).option();
}
@Override
public Location getLocation()
{
return PreferenceInfoHelper.getInfo(this).location();
}
}
public static final String REQUEST_TYPE_ATTRIBUTE = "type";
private static final String VALUE_ATTRIBUTE = "value";
private static final String NAME_ATTRIBUTE = "name";
private static final String MAIN_GROUP = "mainGroup";
private transient Document clientControlerDocument;
private HashMap<String, String> requestHashMap = new HashMap<String, String>();
private String mainGroupName = "default";
private transient ClientRequestXMLProcessor clientRequestXMLProcessor;
private String sessionID;
private HashMap<String, String> sessionHashMap = null;
private String documentURI = null;
@Override
public void init(ClientRequestXMLProcessor clientRequestXMLProcessor,String sessionID,HashMap<String, String> sessionHashMap,String requestName) throws Exception
{
if(isNewSession())
{
this.clientRequestXMLProcessor = clientRequestXMLProcessor;
this.clientControlerDocument = loadClientControlDocument(requestName,sessionHashMap.get("clientID"));
if (this.clientControlerDocument != null)
{
this.documentURI = this.clientControlerDocument.getDocumentURI();
}
this.sessionID = sessionID;
this.sessionHashMap = sessionHashMap;
}
}
public void init(ClientRequestXMLProcessor clientRequestXMLProcessor,String sessionID,Document clientControlerDocument,HashMap<String, String> sessionHashMap) throws Exception
{
if(isNewSession())
{
this.clientRequestXMLProcessor = clientRequestXMLProcessor;
this.clientControlerDocument = clientControlerDocument;
this.sessionID = sessionID;
this.sessionHashMap = sessionHashMap;
}
}
public ClientRequestXMLProcessor getClientRequestXMLProcessor()
{
return clientRequestXMLProcessor;
}
@Override
public void process(ClientRequest clientRequest) throws Exception
{
if (clientControlerDocument == null)
{
return;
}
ClientControllerRequest clientControllerRequest = new ClientControllerRequest(clientRequest);
processClientControllerRequest(clientControllerRequest);
}
private void processClientControllerRequest(ClientControllerRequest clientControllerRequest) throws Exception
{
String clientID = null;
String requestType = null;
try
{
//Figure out the request type
clientID = sessionHashMap.get("clientID");
Element documentElement = clientControllerRequest.getControllerRequestDocument().getDocumentElement();
if (documentElement.hasAttribute("type"))
{
requestType = documentElement.getAttribute("type");
}
loadClientRequestVariables(clientControllerRequest);
loadIdentityVariables(clientID);
//set main group name
if (clientControlerDocument.getDocumentElement().getAttribute(MAIN_GROUP) != null)
{
mainGroupName = clientControlerDocument.getDocumentElement().getAttribute(MAIN_GROUP);
}
//find a main group
((ContextThread)Thread.currentThread()).setContext(clientControlerDocument);
CapoServer.logger.log(Level.FINE, "main group = "+mainGroupName);
Element mainGroupElement = (Element) XPath.selectSingleNode(clientControlerDocument, "server:group('"+mainGroupName+"','"+clientControlerDocument.getDocumentElement().getPrefix()+"')");
if (mainGroupElement == null)
{
mainGroupElement = clientControlerDocument.getDocumentElement();
}
//copy client request as first child to document element
//This is so we can have XPath access to any of the client vars
Element mainGroupDocumentElement = mainGroupElement.getOwnerDocument().getDocumentElement();
mainGroupDocumentElement.insertBefore(mainGroupElement.getOwnerDocument().importNode(clientControllerRequest.getControllerRequestDocument().getDocumentElement(),true),mainGroupDocumentElement.getFirstChild());
//create a new group element out whatever we have discovered
GroupElement groupElement = new GroupElement();
((ContextThread)Thread.currentThread()).setContext(groupElement);
groupElement.init(mainGroupElement, null, null, this);
CapoApplication.logger.log(Level.INFO,"Running request '"+requestType+"' for client:"+clientID);
//run this request
groupElement.processServerSideElement();
((ContextThread)Thread.currentThread()).setContext(null);
}
catch (ControllerProcessingException controllerProcessingException)
{
String message = "error running "+requestType+" ("+documentURI+") request for client: "+clientID+"\n"+controllerProcessingException.getMessage();
CapoServer.logger.log(Level.SEVERE, message);
if (CapoApplication.getApplication().getExceptionList() != null)
{
CapoApplication.getApplication().getExceptionList().add(new Exception(message));
}
}
catch (MissingAttributeException missingAttributeException)
{
String message = "error running "+requestType+" ("+documentURI+") request for client: "+clientID+"\n"+missingAttributeException.getMessage();
CapoServer.logger.log(Level.SEVERE, message);
if (CapoApplication.getApplication().getExceptionList() != null)
{
CapoApplication.getApplication().getExceptionList().add(new Exception(message));
}
}
catch (Exception exception)
{
String message = "error running "+requestType+" ("+documentURI+") request for client: "+clientID;
CapoServer.logger.log(Level.SEVERE, message, exception);
if (CapoApplication.getApplication().getExceptionList() != null)
{
CapoApplication.getApplication().getExceptionList().add(new Exception(message, exception));
}
}
}
private void loadIdentityVariables(String clientID) throws Exception
{
//make sure we are authenticated
if (clientID == null || clientID.isEmpty())
{
return;
}
///check and see if this is an identity push, identity information gets saved for later use
String identityControlName = CapoApplication.getConfiguration().getValue(Preferences.DEFAULT_IDENTITY_FILE);
//make sure we have a document
ResourceDescriptor clientResourceDescriptor = CapoApplication.getDataManager().getResourceDescriptor(null, "clients:"+clientID+"/"+identityControlName);
if (clientResourceDescriptor.getResourceMetaData(null).exists() == false)
{
clientResourceDescriptor.performAction(null, Action.CREATE);
CapoApplication.logger.log(Level.INFO,"Creating new identity document for "+clientID);
XPath.dumpNode(CapoApplication.getDefaultDocument("ids.xml"), clientResourceDescriptor.getOutputStream(null));
clientResourceDescriptor.getOutputStream(null).close();
}
Element identityDocumentElement = CapoApplication.getDocumentBuilder().parse(clientResourceDescriptor.getInputStream(null)).getDocumentElement();
NodeList idElementNodeList = XPath.selectNodes(identityDocumentElement, "//server:id");
for(int index = 0; index < idElementNodeList.getLength(); index++)
{
Element ideElement = (Element) idElementNodeList.item(index);
sessionHashMap.put(ideElement.getAttribute("name"), ideElement.getAttribute("value"));
}
clientResourceDescriptor.release(null);
}
private void loadClientRequestVariables(ClientControllerRequest clientControllerRequest)
{
//load clientRequestVariables
NodeList clientRequestChildren = clientControllerRequest.getControllerRequestDocument().getDocumentElement().getChildNodes();
for (int currentNode = 0; currentNode < clientRequestChildren.getLength();currentNode++)
{
Node node = clientRequestChildren.item(currentNode);
if (node.getNodeType() != Node.ELEMENT_NODE)
{
continue;
}
Element childElement = (Element) node;
String nameAttribute = childElement.getAttribute(NAME_ATTRIBUTE);
String valueAttributeValue = childElement.getAttribute(VALUE_ATTRIBUTE);
if (valueAttributeValue != null && valueAttributeValue.isEmpty() == true)
{
valueAttributeValue = null;
}
if (nameAttribute != null && valueAttributeValue == null && childElement.getTextContent() != null && childElement.getTextContent().isEmpty() == false)
{
valueAttributeValue = childElement.getTextContent();
CapoServer.logger.log(Level.FINE, "seting client var: '"+nameAttribute+"' = '"+valueAttributeValue+"'");
}
else if (nameAttribute != null && valueAttributeValue != null && valueAttributeValue.trim().isEmpty() == false)
{
requestHashMap.put(nameAttribute, valueAttributeValue);
CapoServer.logger.log(Level.FINE, "seting client var: '"+nameAttribute+"' = '"+valueAttributeValue+"'");
}
}
}
public void writeResponse(ControllerResponse controllerResponse) throws Exception
{
clientRequestXMLProcessor.writeResponse(controllerResponse);
}
//SS - Group
public HashMap<String, String> getRequestHashMap()
{
return requestHashMap;
}
@Override
public Document readNextDocument() throws Exception
{
return XPath.unwrapDocument(clientRequestXMLProcessor.readNextDocument(),true);
}
@Override
public String getSessionId()
{
return sessionID;
}
/**
*
* @param clientElement
* @return first child of root element of the clients reply, imported into the original clientElement's document
* @throws Exception
*/
//SS
public Element sendServerSideClientElement(Element clientElement) throws Exception
{
ControllerResponse controllerResponse = createResponse();
controllerResponse.setControlElement(clientElement);
writeResponse(controllerResponse);
//send the the response, and wait for the returned RequestDocument
Document replyDocument = readNextDocument();
//import the returned root element into the original clientElements document
NodeList nodeList = replyDocument.getDocumentElement().getElementsByTagName("*");
if (nodeList.getLength() > 0)
{
return (Element) clientElement.getOwnerDocument().importNode(replyDocument.getDocumentElement(), true);
}
else
{
return null;
}
}
public ControllerResponse createResponse() throws Exception
{
ControllerResponse controllerResponse = new ControllerResponse();
controllerResponse.setSessionID(getSessionId());
return controllerResponse;
}
private Document loadClientControlDocument(String controlerName,String clientID) throws Exception
{
Document localCapoDocument = null;
if (controlerName == null || controlerName.isEmpty())
{
controlerName = CapoApplication.getConfiguration().getValue(Preferences.DEFAULT_CONTROLLER_FILE);
}
localCapoDocument = CapoApplication.getDataManager().findDocument(controlerName, clientID, PREFERENCE.CONTROLLER_DIR);
if (localCapoDocument == null)
{
CapoApplication.logger.log(Level.WARNING,"Couldn't find a controller named "+controlerName);
return null;
}
SchemaFactory schemaFactory = SchemaFactory.newInstance(CapoApplication.XSD11_SCHEMA_LANGUAGE);
Schema schema = schemaFactory.newSchema(new DOMSource(CapoApplication.getDefaultDocument("capo.xsd")));
Validator validator = schema.newValidator();
try
{
validator.validate(new DOMSource(localCapoDocument));
} catch (Exception exception)
{
Node node = (Node) validator.getProperty("http://apache.org/xml/properties/dom/current-element-node");
if (node != null)
{
XPath.dumpNode(localCapoDocument, System.err);
throw new Exception(exception.getMessage() +" on node "+ XPath.getXPath(node) +" in "+controlerName);
}
else
{
throw new Exception(exception.getMessage() +" in "+controlerName);
}
}
//load includes
NodeList includeObjectList = XPath.selectNodes(localCapoDocument, "//server:include");
while(includeObjectList != null && includeObjectList.getLength() != 0)
{
for(int currentNode = 0; currentNode < includeObjectList.getLength();currentNode++)
{
Element includeElement = ((Element)includeObjectList.item(currentNode));
String src = includeElement.getAttribute("src");
if (src == null)
{
includeElement.getParentNode().removeChild(includeElement);
}
else
{
ResourceDescriptor includeResourceDescriptor = CapoApplication.getDataManager().getResourceDescriptor(null,src);
includeResourceDescriptor.addResourceParameters(null, new ResourceParameter(FileResourceType.Parameters.PARENT_PROVIDED_DIRECTORY,PREFERENCE.CONTROLLER_DIR));
if (includeResourceDescriptor.getResourceMetaData(null).exists() == true)
{
Document importDocument = CapoApplication.getDocumentBuilder().parse(includeResourceDescriptor.getInputStream(null));
//validate included XML
try
{
validator.validate(new DOMSource(localCapoDocument));
} catch (Exception exception)
{
Node node = (Node) validator.getProperty("http://apache.org/xml/properties/dom/current-element-node");
if (node != null)
{
throw new Exception(exception.getMessage() +" on node "+ XPath.getXPath(node) +" in "+includeResourceDescriptor.getResourceURI().getResourceURIString());
}
else
{
throw new Exception(exception.getMessage() +" in "+includeResourceDescriptor.getResourceURI().getResourceURIString());
}
}
includeElement.getParentNode().replaceChild(includeElement.getOwnerDocument().importNode(importDocument.getDocumentElement(), true), includeElement);
}
else
{
CapoApplication.logger.log(Level.WARNING,"Couldn't include "+src+" into "+controlerName);
includeElement.getParentNode().removeChild(includeElement);
}
}
}
includeObjectList = XPath.selectNodes(localCapoDocument, "//server:include");
}
return localCapoDocument;
}
public HashMap<String, String> getSessionHashMap()
{
return this.sessionHashMap ;
}
}