/**
Copyright (c) 2012 Delcyon, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.delcyon.capo.xml.dom;
import java.util.HashMap;
import java.util.Vector;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.delcyon.capo.CapoApplication;
import com.delcyon.capo.controller.Group;
import com.delcyon.capo.controller.elements.ResourceControlElement;
import com.delcyon.capo.resourcemanager.ResourceDescriptor;
import com.delcyon.capo.resourcemanager.ResourceParameter;
import com.delcyon.capo.resourcemanager.ResourceParameterBuilder;
import com.delcyon.capo.resourcemanager.ResourceDescriptor.LifeCycle;
import com.delcyon.capo.resourcemanager.ResourceDescriptor.State;
import com.delcyon.capo.util.VariableContainerWrapper;
import com.delcyon.capo.xml.XPath;
import com.delcyon.capo.xml.cdom.CDocument;
import com.delcyon.capo.xml.cdom.CElement;
import com.delcyon.capo.xml.cdom.VariableContainer;
/**
* @author jeremiah
*
*/
public class ResourceDeclarationElement
{
private enum KeyType
{
parent,
child
}
public static final String DECLARATION_PATH_ATTRIBUTE_NAME = "declarationPath";
private Element declaringElement = null;
private ResourceDescriptor resourceDescriptor;
private String localName = null;
private ResourceControlElement resourceControlElement;
private Vector<ResourceDeclarationElement> childResourceDeclarationElementVector = new Vector<ResourceDeclarationElement>();
private Vector<ResourceElement> cacheVector = null;
private HashMap<String, Vector<ResourceElement>> joinHashMap = null;
private ResourceDeclarationElement parent;
private ResourceElement containerResourceElement;
private String declarationPath = null;
private Group parentGroup = null;
public ResourceDeclarationElement(ResourceControlElement resourceControlElement) throws Exception
{
this.parentGroup = resourceControlElement.getParentGroup();
this.resourceControlElement = resourceControlElement;
this.declaringElement = resourceControlElement.getControlElementDeclaration();
if(declaringElement.hasAttribute("name"))
{
localName = declaringElement.getAttribute("name");
}
else
{
localName = resourceControlElement.getName();
}
loadResourceDescriptor();
NodeList childNodeList = XPath.selectNodes(resourceControlElement.getControlElementDeclaration(), "resource:child");
for(int index = 0; index < childNodeList.getLength(); index++)
{
childResourceDeclarationElementVector.add(new ResourceDeclarationElement(this,(Element)childNodeList.item(index)));
}
this.declarationPath = XPath.getXPath(declaringElement).hashCode()+"";
}
private ResourceDeclarationElement(ResourceDeclarationElement parent,Element resourceDeclarationElement) throws Exception
{
this.parent = parent;
this.resourceControlElement = parent.resourceControlElement;
this.declaringElement = resourceDeclarationElement;
if(declaringElement.hasAttribute("name"))
{
localName = declaringElement.getAttribute("name");
}
else
{
localName = resourceControlElement.getName();
}
loadResourceDescriptor();
NodeList childNodeList = XPath.selectNodes(resourceDeclarationElement, "resource:child");
for(int index = 0; index < childNodeList.getLength(); index++)
{
childResourceDeclarationElementVector.add(new ResourceDeclarationElement(this,(Element)childNodeList.item(index)));
}
this.declarationPath = XPath.getXPath(declaringElement).hashCode()+"";
}
private Group getParentGroup() throws Exception
{
if(this.parentGroup == null && parent != null)
{
return parent.getParentGroup();
}
else if (parentGroup != null)
{
return parentGroup;
}
else
{
throw new Exception("ResourceDeclaration has now parent or parent group");
}
}
private void loadResourceDescriptor() throws Exception
{
NamedNodeMap attributeList = declaringElement.getAttributes();
//if we have a URI, then we can go ahead a load a resourceDescriptor for this element
if(attributeList.getNamedItem("uri") != null)
{
this.resourceDescriptor = resourceControlElement.getParentGroup().getResourceDescriptor(resourceControlElement, attributeList.getNamedItem("uri").getNodeValue());
this.resourceControlElement.setResourceDescriptor(resourceDescriptor);
}
else if(attributeList.getNamedItem("path") != null) //check for a path attribute, and ask our parent to load us
{
this.resourceDescriptor = parent.getResourceDescriptor().getChildResourceDescriptor(resourceControlElement, attributeList.getNamedItem("path").getNodeValue());
}
else
{
//we don't have anything, throw an exception for now?
throw new Exception("Must have a uri or a path attribute");
}
//sometimes we may have variables that need to be filled out later, so we can't init or open this yet.
if(declaringElement.getAttribute("dynamic").equalsIgnoreCase("true") == false)
{
resourceDescriptor.init(this,resourceControlElement.getParentGroup(), LifeCycle.GROUP,true,ResourceParameterBuilder.getResourceParameters(resourceControlElement.getControlElementDeclaration()));
resourceDescriptor.open(resourceControlElement.getParentGroup(), ResourceParameterBuilder.getResourceParameters(resourceControlElement.getControlElementDeclaration()));
}
else // this is a dynamic request
{
//so just ignore it, as it will get loaded later.
}
}
public ResourceDescriptor getResourceDescriptor()
{
return resourceDescriptor;
}
public Element getDeclaration()
{
return declaringElement;
}
private String getLocalName()
{
return this.localName ;
}
public ResourceElement buildXML(VariableContainer variableContainer, ResourceParameter... resourceParameters) throws Exception
{
ResourceDocument resourceDocument = new ResourceDocument(resourceControlElement);
resourceDocument.setFullDocument(true);
resourceDocument.setSilenceEvents(true);
ResourceElement rootResourceElement = new ResourceElement(resourceDocument,resourceDocument,resourceDescriptor);
rootResourceElement.setResourceAttribute(DECLARATION_PATH_ATTRIBUTE_NAME, declarationPath);
resourceDocument.setDocumentElement(rootResourceElement);
CDocument testDocument = (CDocument) CapoApplication.getDocumentBuilder().newDocument();
testDocument.setSilenceEvents(true);
Element testRootElement = testDocument.createElement(getLocalName());
testDocument.appendChild(testRootElement);
loadCacheVectors(true,rootResourceElement,testDocument, variableContainer, resourceParameters);
buildXML(rootResourceElement,testRootElement, variableContainer, resourceParameters);
applyResourceContainerGrouping(resourceDocument);
resourceDocument.setSilenceEvents(false);
return rootResourceElement;
}
private void applyResourceContainerGrouping(ResourceDocument document) throws Exception
{
if (containerResourceElement != null)
{
NodeList nodeList = XPath.selectNodes(document, "//*[@"+DECLARATION_PATH_ATTRIBUTE_NAME+"="+declarationPath+" and @container = 'false' and not(../@"+DECLARATION_PATH_ATTRIBUTE_NAME+"="+declarationPath+")]");
HashMap<Node, Node> parentNodeHashMap = new HashMap<Node, Node>();
for(int index = 0; index < nodeList.getLength(); index++)
{
ResourceElement resourceElement = (ResourceElement) nodeList.item(index);
Node parentNode = resourceElement.getParentNode();
ResourceElement localContainerElement = null;
if(parentNodeHashMap.containsKey(parentNode) == false)
{
localContainerElement = (ResourceElement) containerResourceElement.cloneNode(true);
parentNodeHashMap.put(parentNode, localContainerElement);
if(parentNode instanceof ResourceDocument)
{
((ResourceDocument) parentNode).setDocumentElement(localContainerElement);
}
else
{
parentNode.appendChild(localContainerElement);
}
}
else
{
localContainerElement = (ResourceElement) parentNodeHashMap.get(parentNode);
}
localContainerElement.appendChild(resourceElement);
}
}
for (ResourceDeclarationElement childResourceDeclarationElement : childResourceDeclarationElementVector)
{
childResourceDeclarationElement.applyResourceContainerGrouping(document);
}
}
/**
* Scans the resource element document, and runs all resource queries once, while computing any child join values, against the parent path of the join elements.
* @param parentElement
* @param variableContainer
* @param resourceParameters
* @throws Exception
*/
private void loadCacheVectors(boolean recurse,ResourceElement parentResourceElement,CDocument testDocument,VariableContainer variableContainer, ResourceParameter... resourceParameters) throws Exception
{
//implement some caching, since we'll always run the same query, with the same rules for each resourceDescriptor.
//this isn't everything that can be, done but will hold us for a while
Vector<ResourceElement> cacheVector = getCacheVector();
if(resourceDescriptor.getResourceState().ordinal() < State.OPEN.ordinal())
{
System.out.println("found an unititialized resource");
recurse = false;
}
else if(resourceDescriptor.getResourceMetaData(null).exists() == false)
{
//If it doesn't exist, skip it, this is a join after all
//System.out.println("found an unexisting resource: "+resourceDescriptor.getResourceURI());
recurse = false;
}
else if (cacheVector == null)
{
ResourceDocument resourceDocument = parentResourceElement.getOwnerResourceDocument();
cacheVector = new Vector<ResourceElement>();
HashMap<String, Vector<ResourceElement>> joinHashMap = new HashMap<String, Vector<ResourceElement>>();
if (resourceDescriptor.getResourceMetaData(null).isContainer())
{
//we need to keep this around as a parent, for any content
this.containerResourceElement = new ResourceElement(resourceDocument, localName, null, resourceDescriptor.getResourceMetaData(null));
this.containerResourceElement.setResourceAttribute(DECLARATION_PATH_ATTRIBUTE_NAME,declarationPath);
}
//iterate through all of the iterable children
while(resourceDescriptor.next(variableContainer, resourceParameters))
{
//get the result xml from the actual resource descriptor
CElement readXMLElement = (CElement) testDocument.adoptNode(resourceDescriptor.readXML(variableContainer, resourceParameters));
ResourceElement readResourceElement = resourceDocument.createResourceElement(getLocalName(),readXMLElement,resourceDescriptor.getContentMetaData(variableContainer, resourceParameters));
//readResourceElement.setResourceDescriptor(resourceDescriptor); //don't do this, will fail on cloning
readResourceElement.setAttribute("name", getLocalName());
readResourceElement.setResourceAttribute(DECLARATION_PATH_ATTRIBUTE_NAME,declarationPath);
cacheVector.add(readResourceElement);
String key = getHashJoinKey(KeyType.child,readXMLElement, declaringElement);
Vector<ResourceElement> keyMatchVector = joinHashMap.get(key);
if(keyMatchVector == null)
{
keyMatchVector = new Vector<ResourceElement>();
joinHashMap.put(key, keyMatchVector);
}
keyMatchVector.add(readResourceElement);
}
//use our declaring resource element as a place to store the cache data.
setCacheVector(cacheVector);
setJoinHashMap(joinHashMap);
}
if (recurse == true)
{
//get the declared resourceElement children. This is used to define our structure of what we are looking for
//childResourceDeclarationElementVector
for (ResourceDeclarationElement childResourceDeclarationElement : childResourceDeclarationElementVector)
{
childResourceDeclarationElement.loadCacheVectors(true,parentResourceElement,testDocument, variableContainer, resourceParameters);
}
}
}
/**
* This build our xml document by running the results of the loadCacheVectors method, against the resource element document.
* @param parentTestElement - element to append to
* @param variableContainer
* @param resourceParameters
* @return true if children were added to parent element
* @throws Exception
*/
private boolean buildXML(ResourceElement parentResourceElement,Element parentTestElement, VariableContainer variableContainer, ResourceParameter... resourceParameters) throws Exception
{
//System.err.println("======================================START==="+getLocalName()+"===============================");
//XPath.dumpNode(parentElement.getOwnerDocument(), System.err);
boolean isDynamic = false;
//=====================START DYNAMIC CACHE VECTOR=======================================================
if(getJoinHashMap() == null && declaringElement.getAttribute("dynamic").equalsIgnoreCase("true"))
{
isDynamic = true;
VariableContainerWrapper variableContainerWrapper = new VariableContainerWrapper(resourceControlElement.getParentGroup());
NamedNodeMap attributeNamedNodeMap = declaringElement.getAttributes();
String uri = null;
for(int index = 0; index < attributeNamedNodeMap.getLength(); index++)
{
Attr attribute = (Attr) attributeNamedNodeMap.item(index);
if(attribute.getLocalName().matches("(dynamic)|(uri)|(name)|(path)|(joinType)") == false)
{
variableContainerWrapper.setVar(attribute.getLocalName(), XPath.selectSingleNodeValue(parentTestElement, attribute.getValue()));
}
else if(attribute.getLocalName().matches("uri") == true)
{
uri = attribute.getValue();
}
}
ResourceDescriptor originalResourceDescriptor = resourceDescriptor;
if(uri != null)
{
uri = variableContainerWrapper.processVars(uri);
//replace our current resource descriptor with new resource descriptor
resourceDescriptor = CapoApplication.getDataManager().getResourceDescriptor(resourceControlElement, uri); //WE may want this to be ResouceDeclarationElement, and not parent
}
resourceDescriptor.init(this,variableContainerWrapper, LifeCycle.EXPLICIT,true,ResourceParameterBuilder.getResourceParameters(declaringElement));
resourceDescriptor.open(variableContainerWrapper, ResourceParameterBuilder.getResourceParameters(declaringElement));
loadCacheVectors(false,parentResourceElement,(CDocument) parentTestElement.getOwnerDocument(), variableContainerWrapper, resourceParameters);
//done, so reset everything
resourceDescriptor = originalResourceDescriptor;
}
//=====================END DYNAMIC CACHE VECTOR=======================================================
boolean hasChildrenAdded = false;
//get the expected key for this element's parents, now that we have them
String parentJoinResultClause = getHashJoinKey(KeyType.parent,parentTestElement, declaringElement);
if(getJoinHashMap() != null)
{
Vector<ResourceElement> matchingResultVector = getJoinHashMap().get(parentJoinResultClause);
//The first element in the tree is always bad, so process against everything
if(parentTestElement.equals(parentTestElement.getOwnerDocument().getDocumentElement()))
{
matchingResultVector = getCacheVector();
}
//if we had a result in our cache, then walk each element in it
if(matchingResultVector != null)
{
for (ResourceElement element : matchingResultVector)
{
//make a copy, so we don't mess with the originals.
ResourceElement resultElement = (ResourceElement) element.cloneNode(true);
//add it to the parent for testing in the children
parentResourceElement.appendChild(resultElement);
//keep a pointer to see if we were successful within the for loop
boolean resultHadChildrenAdded = false;
//walk each resource declaration, and let them add any children they need to.
for(int index = 0; index < childResourceDeclarationElementVector.size(); index++)
{
//check to make sure we're only dealing with resource declaration elements
ResourceDeclarationElement childResourceDeclarationElement = childResourceDeclarationElementVector.get(index);
//first process the children, for joins, we work from the bottom up.
if(childResourceDeclarationElement.buildXML(resultElement,resultElement.getContent(), variableContainer, resourceParameters) == true)
{
hasChildrenAdded = true;
resultHadChildrenAdded = true;
}
//if we didn't get any children, and we're an inner join, then bail out
else if(childResourceDeclarationElement.getDeclaration().getAttribute("joinType").equalsIgnoreCase("inner") == true)
{
resultHadChildrenAdded = false;
break;
}
}
//if we don't have any declared children, then we won't have any joins, so we should add ourselves, since we did have a match from the cache.
if(childResourceDeclarationElementVector.size() == 0)
{
hasChildrenAdded = true;
resultHadChildrenAdded = true;
}
//clean ourselves up, if we didn't have any children added.
if(resultHadChildrenAdded == false)
{
parentResourceElement.removeChild(resultElement);
}
}
}
}
//if we're dynamic, cleanup our mess we made, so the next dynamic guy will reload as well.
if(isDynamic == true)
{
setCacheVector(null);
setJoinHashMap(null);
}
//System.err.println("======================================END==="+getLocalName()+"===============================");
//XPath.dumpNode(parentElement.getOwnerDocument(), System.err);
return hasChildrenAdded;
}
/**
*
* @param testElement The element that we're going to test against as a context
* @param controlElementDelaration - element that has all of the resource:join children in it.
* @return keys look like ':' separated paths followed by ':' separated values.
* @throws Exception
*/
private String getHashJoinKey(KeyType keyType, Element testElement, Element controlElementDelaration) throws Exception
{
NodeList childResourceElementJoinNodeList = XPath.selectNodes(controlElementDelaration, "resource:join");
StringBuilder valueStringBuilder = new StringBuilder();
StringBuilder parentPathStringBuilder = new StringBuilder();
for(int joinIndex = 0; joinIndex < childResourceElementJoinNodeList.getLength(); joinIndex++)
{
Element joinElement = (Element) childResourceElementJoinNodeList.item(joinIndex);
parentPathStringBuilder.append(joinElement.getAttribute("parent")+":");
String path = null;
if (keyType == KeyType.child)
{
path = joinElement.getAttribute("this");
}
else
{
path = joinElement.getAttribute(keyType.toString());
}
valueStringBuilder.append(XPath.selectSingleNodeValue(testElement, path)+":");
}
parentPathStringBuilder.append(valueStringBuilder.toString());
return parentPathStringBuilder.toString();
}
public Vector<ResourceElement> getCacheVector()
{
return this.cacheVector ;
}
public void setCacheVector(Vector<ResourceElement> cacheVector)
{
this.cacheVector = cacheVector;
}
public void setJoinHashMap(HashMap<String, Vector<ResourceElement>> joinHashMap)
{
this.joinHashMap = joinHashMap;
}
public HashMap<String, Vector<ResourceElement>> getJoinHashMap()
{
return joinHashMap;
}
}