package org.openxdm.xcap.server.slee;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.slee.ActivityContextInterface;
import javax.slee.RolledBackContext;
import javax.slee.SbbContext;
import javax.xml.XMLConstants;
import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.log4j.Logger;
import org.openxdm.xcap.common.appusage.AppUsage;
import org.openxdm.xcap.common.appusage.AuthorizationPolicy;
import org.openxdm.xcap.common.datasource.DataSource;
import org.openxdm.xcap.common.error.BadRequestException;
import org.openxdm.xcap.common.error.CannotDeleteConflictException;
import org.openxdm.xcap.common.error.CannotInsertConflictException;
import org.openxdm.xcap.common.error.ConflictException;
import org.openxdm.xcap.common.error.ConstraintFailureConflictException;
import org.openxdm.xcap.common.error.InternalServerErrorException;
import org.openxdm.xcap.common.error.MethodNotAllowedException;
import org.openxdm.xcap.common.error.NoParentConflictException;
import org.openxdm.xcap.common.error.NotAuthorizedRequestException;
import org.openxdm.xcap.common.error.NotFoundException;
import org.openxdm.xcap.common.error.NotUTF8ConflictException;
import org.openxdm.xcap.common.error.NotValidXMLFragmentConflictException;
import org.openxdm.xcap.common.error.PreconditionFailedException;
import org.openxdm.xcap.common.error.SchemaValidationErrorConflictException;
import org.openxdm.xcap.common.error.UniquenessFailureConflictException;
import org.openxdm.xcap.common.error.UnsupportedMediaTypeException;
import org.openxdm.xcap.common.resource.AttributeResource;
import org.openxdm.xcap.common.resource.DocumentResource;
import org.openxdm.xcap.common.resource.ElementResource;
import org.openxdm.xcap.common.resource.NamespaceBindings;
import org.openxdm.xcap.common.uri.AttributeSelector;
import org.openxdm.xcap.common.uri.DocumentSelector;
import org.openxdm.xcap.common.uri.ElementSelector;
import org.openxdm.xcap.common.uri.ElementSelectorStep;
import org.openxdm.xcap.common.uri.ElementSelectorStepByAttr;
import org.openxdm.xcap.common.uri.ElementSelectorStepByPos;
import org.openxdm.xcap.common.uri.ElementSelectorStepByPosAttr;
import org.openxdm.xcap.common.uri.NamespaceSelector;
import org.openxdm.xcap.common.uri.NodeSelector;
import org.openxdm.xcap.common.uri.ParseException;
import org.openxdm.xcap.common.uri.Parser;
import org.openxdm.xcap.common.uri.ResourceSelector;
import org.openxdm.xcap.common.uri.TerminalSelector;
import org.openxdm.xcap.common.xml.NamespaceContext;
import org.openxdm.xcap.common.xml.TextWriter;
import org.openxdm.xcap.common.xml.XMLValidator;
import org.openxdm.xcap.server.etag.ETagGenerator;
import org.openxdm.xcap.server.etag.ETagValidator;
import org.openxdm.xcap.server.result.CreatedWriteResult;
import org.openxdm.xcap.server.result.OKWriteResult;
import org.openxdm.xcap.server.result.ReadResult;
import org.openxdm.xcap.server.result.WriteResult;
import org.openxdm.xcap.server.slee.resource.appusagecache.AppUsageCacheResourceAdaptorSbbInterface;
import org.openxdm.xcap.server.slee.resource.datasource.DataSourceSbbInterface;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public abstract class RequestProcessorSbb implements
RequestProcessorSbbLocalObject, javax.slee.Sbb {
private SbbContext sbbContext = null; // This SBB's context
private Context myEnv = null; // This SBB's environment
private static Logger logger = Logger.getLogger(RequestProcessorSbb.class);
private AppUsageCacheResourceAdaptorSbbInterface appUsageCache;
private DataSourceSbbInterface dataSourceSbbInterface = null;
/**
* if true a put request for a non existent user will insert user before
* request is processed
*/
private boolean dynamicUserProvisioning = false;
/**
* Called when an sbb object is instantied and enters the pooled state.
*/
public void setSbbContext(SbbContext context) {
if (logger.isDebugEnabled())
logger.debug("setSbbContext(context=" + context.toString() + ")");
this.sbbContext = context;
try {
myEnv = (Context) new InitialContext().lookup("java:comp/env");
appUsageCache = (AppUsageCacheResourceAdaptorSbbInterface) myEnv
.lookup("slee/resources/openxdm/appusagecache/sbbrainterface");
dataSourceSbbInterface = (DataSourceSbbInterface) myEnv
.lookup("slee/resources/openxdm/datasource/sbbrainterface");
dynamicUserProvisioning = ((Boolean) myEnv
.lookup("dynamicUserProvisioning")).booleanValue();
} catch (NamingException e) {
logger.error("Can't set sbb context.", e);
}
}
public void unsetSbbContext() {
if (logger.isDebugEnabled())
logger.debug("unsetSbbContext()");
this.sbbContext = null;
}
public void sbbCreate() throws javax.slee.CreateException {
}
public void sbbPostCreate() throws javax.slee.CreateException {
}
public void sbbActivate() {
}
public void sbbPassivate() {
}
public void sbbRemove() {
}
public void sbbLoad() {
}
public void sbbStore() {
}
public void sbbExceptionThrown(Exception exception, Object event,
ActivityContextInterface activity) {
if (logger.isDebugEnabled())
logger.debug("sbbExceptionThrown(exception=" + exception.toString()
+ ",event=" + event.toString() + ",activity="
+ activity.toString() + ")");
}
public void sbbRolledBack(RolledBackContext sbbRolledBack) {
if (logger.isDebugEnabled())
logger.debug("sbbRolledBack(sbbRolledBack="
+ sbbRolledBack.toString() + ")");
}
protected SbbContext getSbbContext() {
return sbbContext;
}
// APPUSAGE CACHE ITERACTION
// SERVICE LOGIC
// #############################################################
public WriteResult delete(ResourceSelector resourceSelector,
ETagValidator eTagValidator, String xcapRoot, String authenticatedUser)
throws NotFoundException, InternalServerErrorException,
BadRequestException, CannotDeleteConflictException,
PreconditionFailedException, MethodNotAllowedException,
SchemaValidationErrorConflictException,
UniquenessFailureConflictException,
ConstraintFailureConflictException, NotAuthorizedRequestException {
WriteResult result = new OKWriteResult();
DocumentSelector documentSelector = null;
NodeSelector nodeSelector = null;
AttributeSelector attributeSelector = null;
Map<String, String> namespaces = null;
AppUsage appUsage = null;
try {
// parse document parent String
documentSelector = Parser.parseDocumentSelector(resourceSelector
.getDocumentSelector());
// get app usage from cache
appUsage = appUsageCache.borrow(documentSelector.getAUID());
if (appUsage == null) {
// throw exception
if (logger.isDebugEnabled())
logger.debug("appusage not found");
throw new NotFoundException();
}
// authorize user
if (authenticatedUser != null && !appUsage.getAuthorizationPolicy().isAuthorized(authenticatedUser, AuthorizationPolicy.Operation.DELETE, documentSelector)) {
throw new NotAuthorizedRequestException();
}
// get document
org.openxdm.xcap.common.datasource.Document document = dataSourceSbbInterface
.getDocument(documentSelector);
if (document == null) {
// throw exception
if (logger.isDebugEnabled())
logger.debug("document not found");
throw new NotFoundException();
} else {
// document exists
if (logger.isDebugEnabled())
logger.debug("document found");
// get as dom
Document domDocument = document.getAsDOMDocument();
// check document etag
if (eTagValidator != null) {
eTagValidator.validate(document.getETag());
if (logger.isDebugEnabled())
logger.debug("document etag found and validated");
} else {
if (logger.isDebugEnabled())
logger.debug("document etag not found");
}
// check node selector string from resource selector
if (resourceSelector.getNodeSelector() != null) {
// elem, attrib or namespace bind
// parse node selector
nodeSelector = Parser.parseNodeSelector(resourceSelector
.getNodeSelector());
if (logger.isDebugEnabled())
logger.debug("node selector found and parsed");
// create xpath
XPath xpath = XPathFactory.newInstance().newXPath();
// get namespaces bindings from resource selector
namespaces = resourceSelector.getNamespaces();
// get default doc namespace binding for app usage and add
// it to namespace bindings for empty prefix
namespaces.put(XMLConstants.DEFAULT_NS_PREFIX, appUsage
.getDefaultDocumentNamespace());
// add a namespace context to xpath to resolve bindings
NamespaceContext nsContext = new NamespaceContext(
namespaces);
xpath.setNamespaceContext(nsContext);
if (logger.isDebugEnabled())
logger.debug("xpath initiated with namespace context");
try {
// exec query to get element
NodeList elementNodeList = (NodeList) xpath.evaluate(
nodeSelector
.getElementSelectorWithEmptyPrefix(),
domDocument, XPathConstants.NODESET);
if (elementNodeList.getLength() > 1) { // MULTIPLE
// ELEMENTS
if (logger.isDebugEnabled())
logger
.debug("xpath query returned more than one element, returning not found");
throw new NotFoundException();
}
else if (elementNodeList.getLength() == 1) {
if (logger.isDebugEnabled())
logger
.debug("xpath query returned one element as expected");
Element element = (Element) elementNodeList.item(0);
if (nodeSelector.getTerminalSelector() != null) {
// parse terminal selector
TerminalSelector terminalSelector = Parser
.parseTerminalSelector(nodeSelector
.getTerminalSelector());
if (logger.isDebugEnabled())
logger
.debug("terminal selector found and parsed");
if (terminalSelector instanceof AttributeSelector) {
if (logger.isDebugEnabled())
logger
.debug("terminal selector is an attribute selector");
attributeSelector = (AttributeSelector) terminalSelector;
// attribute selector, get attribute
String attrName = attributeSelector
.getAttName();
if (element.hasAttribute(attrName)) {
// exists, delete it
element.removeAttribute(attrName);
if (logger.isDebugEnabled())
logger
.debug("attribute found and deleted");
} else {
// does not exists
if (logger.isDebugEnabled())
logger
.debug("attribute to delete not found");
throw new NotFoundException();
}
} else if (terminalSelector instanceof NamespaceSelector) {
// onle GET method is allowed for a
// namespace selector
if (logger.isDebugEnabled())
logger
.debug("terminal selector is a namespace selector, not allowed on delete");
Map<String, String> map = new HashMap<String, String>();
map.put("Allow", "GET");
throw new MethodNotAllowedException(map);
}
else {
// unknown terminal selector
if (logger.isDebugEnabled())
logger
.debug("unknow terminal selector");
throw new InternalServerErrorException(
"unknown terminal selector");
}
} else { // DELETE ELEMENT
if (logger.isDebugEnabled())
logger
.debug("terminal selector not found, delete of an element");
// check cannot delete
ElementSelectorStep lastElementSelectorStep = Parser
.parseLastElementSelectorStep(nodeSelector
.getElementSelector());
if (lastElementSelectorStep instanceof ElementSelectorStepByPosAttr) {
// need to check if it's the last sibring
// with the same name and attr value
ElementSelectorStepByPosAttr elementSelectorStepByPosAttr = (ElementSelectorStepByPosAttr) lastElementSelectorStep;
if (elementSelectorStepByPosAttr.getName()
.equals("*")) {
if (logger.isDebugEnabled())
logger
.debug("element selector by attr and pos with wildcard name");
// all elements wildcard
Element siblingElement = element;
while ((siblingElement = (Element) siblingElement
.getNextSibling()) != null) {
// get attribute with same name
Attr siblingElementAttr = siblingElement
.getAttributeNode(elementSelectorStepByPosAttr
.getAttrName());
// check if it has the same value
if (siblingElementAttr != null
&& siblingElementAttr
.getValue()
.equals(
elementSelectorStepByPosAttr
.getAttrValue())) {
// we have a sibling with the
// same attribute with the same
// value, so when we delete the
// element the uri points to
// this one
if (logger.isDebugEnabled())
logger
.debug("sibling element with same attr name and value, cannot delete");
throw new CannotDeleteConflictException();
}
}
} else {
if (logger.isDebugEnabled())
logger
.debug("element selector by attr and pos without wildcard name");
Element siblingElement = element;
while ((siblingElement = (Element) siblingElement
.getNextSibling()) != null) {
if (element
.getNodeName()
.compareTo(
siblingElement
.getNodeName()) == 0
&& element
.getNamespaceURI()
.compareTo(
siblingElement
.getNamespaceURI()) == 0) {
// sibling with the same name
// get attribute with same name
Attr siblingElementAttr = siblingElement
.getAttributeNode(elementSelectorStepByPosAttr
.getAttrName());
// check if it has the same
// value
if (siblingElementAttr != null
&& siblingElementAttr
.getValue()
.equals(
elementSelectorStepByPosAttr
.getAttrValue())) {
// we have a sibling with
// the same attribute with
// the same value, so when
// we delete the element the
// uri points to this one
if (logger.isDebugEnabled())
logger
.debug("sibling element with same attr name and value, cannot delete");
throw new CannotDeleteConflictException();
}
}
}
}
} else if (lastElementSelectorStep instanceof ElementSelectorStepByPos) {
ElementSelectorStepByPos elementSelectorStepByPos = (ElementSelectorStepByPos) lastElementSelectorStep;
/*
* In particular, if a DELETE operation
* refers to an element by name and position
* alone (parent/elname[n]), this is
* permitted only when the element to be
* deleted is the last element amongst all
* its siblings with that name. Similarly,
* if a DELETE operation refers to an
* element by position alone (parent/*[n]),
* this is permitted only when the elemented
* to be deleted is the last amongst all
* sibling elements, regardless of name.
*/
// find out if it's the last sibling
if (elementSelectorStepByPos.getName()
.equals("*")) {
if (logger.isDebugEnabled())
logger
.debug("element selector by pos with wildcard name");
if (element.getNextSibling() != null) {
// not the last * sibling
if (logger.isDebugEnabled())
logger
.debug("not the last * sibling, cannot delete");
throw new CannotDeleteConflictException();
}
} else {
if (logger.isDebugEnabled())
logger
.debug("element selector by pos without wildcard name");
// search a next sibling with the same
// name
Element siblingElement = element;
while ((siblingElement = (Element) siblingElement
.getNextSibling()) != null) {
if (element
.getNodeName()
.compareTo(
siblingElement
.getNodeName()) == 0
&& element
.getNamespaceURI()
.compareTo(
siblingElement
.getNamespaceURI()) == 0) {
if (logger.isDebugEnabled())
logger
.debug("sibling element with same name and ns after the selected element,cannot delete");
throw new CannotDeleteConflictException();
}
}
}
}
if (logger.isDebugEnabled())
logger.debug("element deleted");
// the element can be deleted
element.getParentNode().removeChild(element);
}
} else { // ELEMENT DOES NOT EXIST
if (logger.isDebugEnabled())
logger.debug("element not found");
throw new NotFoundException();
}
if (logger.isDebugEnabled())
logger.debug("validating document after delete");
// validate the updated document against it's schema
appUsage.validateSchema(domDocument);
} catch (XPathExpressionException e) {
// error in xpath expression
if (logger.isDebugEnabled())
logger.debug("error in xpath expression.");
if (nodeSelector
.elementSelectorHasUnbindedPrefixes(namespaces)) {
// element selector has unbinded prefixe(s)
if (logger.isDebugEnabled())
logger
.debug("element selector doesn't have prefixe(s) bound, bad request");
throw new BadRequestException();
} else {
// nothing wrong with prefixes, return not found
// exception
if (logger.isDebugEnabled())
logger.debug("element not found");
throw new NotFoundException();
}
}
}
if (logger.isDebugEnabled())
logger
.debug("checking app usage constraints and resource interdependencies...");
// verify app usage constraints
appUsage.checkConstraintsOnDelete(domDocument, xcapRoot,
documentSelector, dataSourceSbbInterface);
// process resource interdependencies
appUsage.processResourceInterdependenciesOnDelete(domDocument,
documentSelector, dataSourceSbbInterface);
// last delete or update the doc
if (resourceSelector.getNodeSelector() == null) {
// delete document
if (logger.isDebugEnabled())
logger
.debug("node selector not found, delete of a document");
dataSourceSbbInterface.deleteDocument(documentSelector,
document.getETag());
} else {
// update document
// create new etag
String newETag = ETagGenerator.generate(resourceSelector
.getDocumentSelector());
// update data source with document
try {
String xml = TextWriter.toString(domDocument);
if (attributeSelector == null) {
dataSourceSbbInterface.updateElement(
documentSelector, nodeSelector, namespaces,
document.getETag(), newETag, xml,
domDocument, null, null);
} else {
dataSourceSbbInterface.updateAttribute(
documentSelector, nodeSelector,
attributeSelector, namespaces, document
.getETag(), newETag, xml,
domDocument, null);
}
if (logger.isDebugEnabled())
logger.debug("document updated in data source");
} catch (Exception e) {
throw new InternalServerErrorException(
"Failed to serialize resulting dom document to string");
}
// add it to the result
result.setResponseEntityTag(newETag);
}
if (logger.isDebugEnabled())
logger.debug("delete request processed with sucess");
// return result
return result;
}
} catch (ParseException e) {
if (logger.isDebugEnabled())
logger.debug("error parsing uri, returning not found");
throw new NotFoundException();
} catch (InterruptedException e) {
String msg = "failed to borrow app usage object from cache";
logger.error(msg, e);
throw new InternalServerErrorException(msg);
} finally {
if (appUsage != null) {
appUsageCache.release(appUsage);
}
}
}
public ReadResult get(ResourceSelector resourceSelector, String authenticatedUser)
throws NotFoundException, InternalServerErrorException,
BadRequestException, NotAuthorizedRequestException {
AppUsage appUsage = null;
try {
// parse document parent String
DocumentSelector documentSelector = Parser
.parseDocumentSelector(resourceSelector
.getDocumentSelector());
// get app usage from cache
appUsage = appUsageCache.borrow(documentSelector.getAUID());
if (appUsage == null) {
// throw exception
if (logger.isDebugEnabled())
logger.debug("appusage not found");
throw new NotFoundException();
}
// authorize user
if (authenticatedUser != null && !appUsage.getAuthorizationPolicy().isAuthorized(authenticatedUser, AuthorizationPolicy.Operation.GET, documentSelector)) {
throw new NotAuthorizedRequestException();
}
// get document
org.openxdm.xcap.common.datasource.Document document = dataSourceSbbInterface
.getDocument(documentSelector);
if (document == null) {
// throw exception
if (logger.isDebugEnabled())
logger.debug("document not found");
throw new NotFoundException();
}
if (logger.isDebugEnabled())
logger.debug("document found");
// get document's etag
String eTag = document.getETag();
// check node selector string from resource selector
if (resourceSelector.getNodeSelector() != null) {
// elem, attrib or namespace bind
// parse node selector
NodeSelector nodeSelector = Parser
.parseNodeSelector(resourceSelector.getNodeSelector());
if (logger.isDebugEnabled())
logger.debug("node selector found and parsed");
// create xpath
XPath xpath = XPathFactory.newInstance().newXPath();
// get namespaces bindings from resource selector
Map<String, String> namespaces = resourceSelector
.getNamespaces();
// get default doc namespace binding for app usage and add it to
// namespace bindings for empty prefix
namespaces.put(XMLConstants.DEFAULT_NS_PREFIX, appUsage
.getDefaultDocumentNamespace());
// add a namespace context to xpath to resolve bindings
NamespaceContext nsContext = new NamespaceContext(namespaces);
xpath.setNamespaceContext(nsContext);
if (logger.isDebugEnabled())
logger.debug("xpath initiated with namespace context");
// get document as dom
org.w3c.dom.Document domDocument = document.getAsDOMDocument();
try {
// exec query for element
NodeList elementNodeList = (NodeList) xpath.evaluate(
nodeSelector.getElementSelectorWithEmptyPrefix(),
domDocument, XPathConstants.NODESET);
if (elementNodeList.getLength() > 1) { // MULTIPLE ELEMENTS
if (logger.isDebugEnabled())
logger
.debug("xpath query returned more than one element, returning not found");
throw new NotFoundException();
}
else if (elementNodeList.getLength() == 1) {
if (logger.isDebugEnabled())
logger
.debug("xpath query returned one element as expected");
Element element = (Element) elementNodeList.item(0);
if (nodeSelector.getTerminalSelector() != null) {
// parse terminal selector
TerminalSelector terminalSelector = Parser
.parseTerminalSelector(nodeSelector
.getTerminalSelector());
if (logger.isDebugEnabled())
logger
.debug("terminal selector found and parsed");
if (terminalSelector instanceof AttributeSelector) {
// attribute selector, get attribute
if (logger.isDebugEnabled())
logger
.debug("terminal selector is an attribute selector");
Attr attr = element
.getAttributeNode(((AttributeSelector) terminalSelector)
.getAttName());
if (attr != null) {
// exists, return its value
if (logger.isDebugEnabled())
logger
.debug("attribute found, returning result");
return new ReadResult(eTag,
new AttributeResource(attr
.getNodeValue()));
} else {
// does not exists
if (logger.isDebugEnabled())
logger
.debug("attribute to retreive not found");
throw new NotFoundException();
}
} else if (terminalSelector instanceof NamespaceSelector) {
// namespace selector, get namespace bindings
if (logger.isDebugEnabled())
logger
.debug("terminal selector is a namespace selector");
return new ReadResult(eTag,
getNamespaceBindings(element, element
.getLocalName(),
resourceSelector
.getNamespaces()));
} else {
// invalid terminal selector
if (logger.isDebugEnabled())
logger.debug("unknow terminal selector");
throw new NotFoundException();
}
} else {
// element
if (logger.isDebugEnabled())
logger
.debug("terminal selector not found, returining result with the element found");
return new ReadResult(eTag, new ElementResource(
TextWriter.toString(element)));
}
} else { // ELEMENT DOES NOT EXIST
if (logger.isDebugEnabled())
logger.debug("element not found");
throw new NotFoundException();
}
} catch (XPathExpressionException e) {
// error in xpath expression
if (logger.isDebugEnabled())
logger.debug("error in xpath expression.");
if (nodeSelector
.elementSelectorHasUnbindedPrefixes(namespaces)) {
// element selector has unbinded prefixe(s)
if (logger.isDebugEnabled())
logger
.debug("element selector doesn't have prefixe(s) bound, bad request");
throw new BadRequestException();
} else {
// nothing wrong with prefixes, return not found
// exception
if (logger.isDebugEnabled())
logger.debug("element not found");
throw new NotFoundException();
}
}
} else {
// no node selector, just get the document
if (logger.isDebugEnabled())
logger
.debug("node selector not found, returning the document");
return new ReadResult(eTag, new DocumentResource(document
.getAsString(), appUsage.getMimetype()));
}
} catch (ParseException e) {
if (logger.isDebugEnabled())
logger.debug("error in parsing uri.");
throw new NotFoundException();
} catch (TransformerException e) {
logger.error("unable to transform dom element to text.", e);
throw new InternalServerErrorException(e.getMessage());
} catch (InterruptedException e) {
String msg = "failed to borrow app usage object from cache";
logger.error(msg, e);
throw new InternalServerErrorException(msg);
} finally {
if (appUsage != null) {
appUsageCache.release(appUsage);
}
}
}
private NamespaceBindings getNamespaceBindings(Node element,
String elementName, Map<String, String> namespacesToGet)
throws NotFoundException {
boolean done = false;
// init result namespaces map
Map<String, String> result = new HashMap<String, String>();
// remove empty prefix from "namespaces to get" map
namespacesToGet.remove("");
// create set of namespaces uri to get
Collection<String> namespacesUris = namespacesToGet.values();
while (done == false && element.getNodeType() == Node.ELEMENT_NODE) {
// get element attributes
NamedNodeMap elementAttributes = element.getAttributes();
// process each one
for (int i = 0; i < elementAttributes.getLength(); i++) {
Node attributeNode = elementAttributes.item(i);
if (attributeNode.getNodeName().compareTo("xmlns") == 0
|| attributeNode.getPrefix().compareTo("xmlns") == 0) {
// its a namespace
if (namespacesUris.contains(attributeNode.getNodeValue())) {
// it was requested, add it to the result map
result.put(attributeNode.getNodeName(), attributeNode
.getNodeValue());
if (result.size() == namespacesUris.size()) {
done = true;
break;
}
}
}
}
// move to parent
element = element.getParentNode();
}
if (!done) {
// at least one was not found
if (logger.isDebugEnabled())
logger
.debug("didn't found any namespace binding, returning not found");
throw new NotFoundException();
} else {
// return namespace bindings
if (logger.isDebugEnabled())
logger.debug("found namespace binding(s)");
return new NamespaceBindings(elementName, result);
}
}
public WriteResult put(ResourceSelector resourceSelector, String mimetype,
InputStream contentStream, ETagValidator eTagValidator,
String xcapRoot, String authenticatedUser) throws ConflictException,
MethodNotAllowedException, UnsupportedMediaTypeException,
InternalServerErrorException, PreconditionFailedException,
BadRequestException, NotAuthorizedRequestException {
WriteResult result = null;
DocumentSelector documentSelector = null;
NodeSelector nodeSelector = null;
AttributeSelector attributeSelector = null;
Map<String, String> namespaces = null;
Document domDocument = null;
String attributeValue = null;
String newElementAsString = null;
Element newElement = null;
AppUsage appUsage = null;
try {
// parse document selector
documentSelector = Parser.parseDocumentSelector(resourceSelector
.getDocumentSelector());
// get app usage from cache
appUsage = appUsageCache.borrow(documentSelector.getAUID());
if (appUsage == null) {
// throw exception
if (logger.isDebugEnabled())
logger.debug("appusage not found");
throw new NoParentConflictException(xcapRoot);
}
// authorize user
if (authenticatedUser != null && !appUsage.getAuthorizationPolicy().isAuthorized(authenticatedUser, AuthorizationPolicy.Operation.PUT, documentSelector)) {
throw new NotAuthorizedRequestException();
}
if (dynamicUserProvisioning) {
// creates user if does not exist
String[] appUsageCollections = dataSourceSbbInterface
.getCollections(appUsage.getAUID());
boolean found = false;
for (String collection : appUsageCollections) {
if (collection.equals(documentSelector.getDocumentParent())) {
found = true;
break;
}
}
if (!found) {
dataSourceSbbInterface.addCollection(appUsage.getAUID(),
documentSelector.getDocumentParent());
}
}
// try to get document's resource
org.openxdm.xcap.common.datasource.Document document = dataSourceSbbInterface
.getDocument(documentSelector);
if (document == null) { // DOCUMENTS DOES NOT EXIST
if (logger.isDebugEnabled())
logger.debug("document not found");
// check parent exists
String existingParent = dataSourceSbbInterface
.getExistingCollection(documentSelector.getAUID(),
documentSelector.getDocumentParent());
if (!existingParent
.equals(documentSelector.getDocumentParent())) {
throw new NoParentConflictException(xcapRoot + "/"
+ documentSelector.getAUID() + "/" + existingParent);
}
if (resourceSelector.getNodeSelector() != null) {
// we have a node selector, throw exception with ancestor,
// which we already know it's the document parent
if (logger.isDebugEnabled())
logger.debug("node selector found, no parent conflict");
throw new NoParentConflictException(xcapRoot
+ documentSelector.getCompleteDocumentParent());
}
else { // PUT NEW DOCUMENT
if (logger.isDebugEnabled())
logger
.debug("node selector not found, put of a document");
if (mimetype == null
|| !mimetype.equals(appUsage.getMimetype())) {
// mimetype is not valid
if (logger.isDebugEnabled())
logger
.debug("invalid mimetype, does not matches the app usage");
throw new UnsupportedMediaTypeException();
}
// verify if content is utf-8
Reader utf8reader = XMLValidator
.getUTF8Reader(contentStream);
if (logger.isDebugEnabled())
logger.debug("document content is utf-8");
// create document
domDocument = XMLValidator
.getWellFormedDocument(utf8reader);
if (logger.isDebugEnabled())
logger.debug("document content parsed with sucess");
// create result
result = new CreatedWriteResult();
}
} else { // DOCUMENT EXISTS
if (logger.isDebugEnabled())
logger.debug("document found");
// get as dom
domDocument = document.getAsDOMDocument();
// check document etag
if (eTagValidator != null) {
eTagValidator.validate(document.getETag());
if (logger.isDebugEnabled())
logger.debug("document etag found and validated");
} else {
if (logger.isDebugEnabled())
logger.debug("document etag not found");
}
if (resourceSelector.getNodeSelector() != null) { // PUT
// ELEMENT
// OR ATTR
// create xpath
XPath xpath = XPathFactory.newInstance().newXPath();
// get namespaces bindings from resource selector
namespaces = resourceSelector.getNamespaces();
// get default doc namespace binding for app usage and add
// it to namespace bindings for empty prefix
namespaces.put(XMLConstants.DEFAULT_NS_PREFIX, appUsage
.getDefaultDocumentNamespace());
// add a namespace context to xpath to resolve bindings
NamespaceContext nsContext = new NamespaceContext(
namespaces);
xpath.setNamespaceContext(nsContext);
if (logger.isDebugEnabled())
logger.debug("xpath initiated with namespace context");
try {
// parse node selector
nodeSelector = Parser
.parseNodeSelector(resourceSelector
.getNodeSelector());
if (logger.isDebugEnabled())
logger.debug("node selector found and parsed");
String elementSelectorWithEmptyPrefix = nodeSelector
.getElementSelectorWithEmptyPrefix();
try {
// exec query for element
NodeList elementNodeList = (NodeList) xpath
.evaluate(elementSelectorWithEmptyPrefix,
domDocument, XPathConstants.NODESET);
if (elementNodeList.getLength() > 1) { // MULTIPLE
// ELEMENTS
if (logger.isDebugEnabled())
logger
.debug("xpath query returned more than one element, no parent conflict");
throw new NoParentConflictException(
getElementExistentAncestor(xcapRoot,
resourceSelector
.getDocumentSelector(),
elementSelectorWithEmptyPrefix,
domDocument, xpath));
}
else if (elementNodeList.getLength() == 1) { // ELEMENT
// EXISTS
if (logger.isDebugEnabled())
logger
.debug("xpath query returned one element as expected");
Element element = (Element) elementNodeList
.item(0);
if (nodeSelector.getTerminalSelector() != null) { // PUT
// ATTR
// ?
try {
// parse terminal selector
TerminalSelector terminalSelector = Parser
.parseTerminalSelector(nodeSelector
.getTerminalSelector());
if (logger.isDebugEnabled())
logger
.debug("terminal selector found and parsed");
if (terminalSelector instanceof AttributeSelector) { // PUT
// ATTR
// CONFIRMED
if (logger.isDebugEnabled())
logger
.debug("terminal selector is an attribute selector");
// verify mimetype
if (mimetype == null
|| !mimetype
.equals(AttributeResource.MIMETYPE)) {
// mimetype is not correct
throw new UnsupportedMediaTypeException();
}
// read and verify if attribute
// value is utf-8
attributeValue = XMLValidator
.getUTF8String(contentStream);
if (logger.isDebugEnabled())
logger
.debug("attr content is utf-8");
// verify if attribute value is
// valid AttValue
XMLValidator
.checkAttValue(attributeValue);
if (logger.isDebugEnabled())
logger
.debug("attr value is valid AttValue");
// get attribute name
attributeSelector = (AttributeSelector) terminalSelector;
String attributeName = attributeSelector
.getAttName();
// get attribute
Attr attribute = element
.getAttributeNode(attributeName);
if (attribute != null) { // ATTR
// EXISTS
if (logger.isDebugEnabled())
logger
.debug("attr found in document");
try {
// verify if cannot insert,
// e.g .../x[id1="1"]/@id1
// and attValue = 2
ElementSelectorStep lastElementSelectorStep = Parser
.parseLastElementSelectorStep(nodeSelector
.getElementSelector());
if (lastElementSelectorStep instanceof ElementSelectorStepByAttr) {
ElementSelectorStepByAttr elementSelectorByAttr = (ElementSelectorStepByAttr) lastElementSelectorStep;
// if this step attr
// name is the specified
// attrName and this
// step attrValue is not
// the same as the
// specified attr value,
// it cannot insert
if (elementSelectorByAttr
.getAttrName()
.equals(
attributeName)
&& !elementSelectorByAttr
.getAttrValue()
.equals(
attributeValue)) {
if (logger
.isDebugEnabled())
logger
.debug("element selector's last step attr name is the specified attrName and this step attrValue is not the same as the specified attr value, cannot insert");
throw new CannotInsertConflictException();
}
}
} catch (ParseException e) {
// this shouldn't happen
logger
.error(
"error parsing last element selector step",
e);
throw new InternalServerErrorException(
"error parsing last element selector step");
}
// create result
result = new OKWriteResult();
} else { // ATTR DOES NOT EXISTS
if (logger.isDebugEnabled())
logger
.debug("attr not found in document");
result = new CreatedWriteResult();
}
// set attribute
element.setAttribute(attributeName,
attributeValue);
// element.setAttributeNS(namespace,
// attributeName,attributeValue);
if (logger.isDebugEnabled())
logger.debug("attr set");
}
else if (terminalSelector instanceof NamespaceSelector) { // PUT
// NAMESPACE
// BINDINGS
// onle GET method is allowed for a
// namespace selector
if (logger.isDebugEnabled())
logger
.debug("terminal selector is a namespace selector, not allowed on put");
Map<String, String> map = new HashMap<String, String>();
map.put("Allow", "GET");
throw new MethodNotAllowedException(
map);
}
else {
// unknown terminal selector
if (logger.isDebugEnabled())
logger
.debug("unknown terminal selector");
throw new InternalServerErrorException(
"unknown terminal selector");
}
} catch (ParseException e) {
// invalid terminal selector, existing
// ancestor is the element
if (logger.isDebugEnabled())
logger
.debug("failed to parse terminal selector, returning no parent conflict with element as ancestor");
throw new NoParentConflictException(
xcapRoot
+ resourceSelector
.getDocumentSelector()
+ "/~~"
+ nodeSelector
.getElementSelector());
}
}
else { // REPLACE ELEMENT
if (logger.isDebugEnabled())
logger.debug("element found");
if (mimetype == null
|| !mimetype
.equals(ElementResource.MIMETYPE)) {
// mimetype is not correct
throw new UnsupportedMediaTypeException();
}
// read and verify if content value is utf-8
newElementAsString = XMLValidator
.getUTF8String(contentStream);
if (logger.isDebugEnabled())
logger.debug("content is utf-8");
// create XML fragment node
newElement = XMLValidator
.getWellFormedDocumentFragment(new StringReader(
newElementAsString));
if (logger.isDebugEnabled())
logger
.debug("content is well formed document fragment");
try {
// verify if cannot insert
ElementSelectorStep lastElementSelectorStep = Parser
.parseLastElementSelectorStep(nodeSelector
.getElementSelector());
// if element's tag name is not equal to
// this step's name then cannot insert
if (!newElement.getTagName().equals(
lastElementSelectorStep
.getName())) {
if (logger.isDebugEnabled())
logger
.debug("element's tag name is not equal to this step's name, cannot insert");
throw new CannotInsertConflictException();
}
if (lastElementSelectorStep instanceof ElementSelectorStepByAttr) {
ElementSelectorStepByAttr elementSelectorStepByAttr = (ElementSelectorStepByAttr) lastElementSelectorStep;
// check attr value
String elementAttrValue = newElement
.getAttribute(elementSelectorStepByAttr
.getAttrName());
if (elementAttrValue == null
|| !elementAttrValue
.equals(elementSelectorStepByAttr
.getAttrValue())) {
if (logger.isDebugEnabled())
logger
.debug("element selector's last step has an attr and it's new value changes this attr value, cannot insert");
throw new CannotInsertConflictException();
}
}
// import the element node
newElement = (Element) domDocument
.importNode(newElement, true);
// replace node
element.getParentNode().replaceChild(
newElement, element);
// create result
result = new OKWriteResult();
if (logger.isDebugEnabled())
logger.debug("element replaced");
} catch (ParseException e) {
// MUST not come here, the element was
// found
logger
.error(
"the element was found but the parsing of the element selector's last step thrown an error",
e);
throw new InternalServerErrorException(
"the element was found but the parsing of the element selector's last step thrown an error");
}
}
}
else { // ELEMENT NOT FOUND
if (logger.isDebugEnabled())
logger.debug("element not found");
if (nodeSelector.getTerminalSelector() != null) {
// throw no parent exception since there is
// no element but we have a terminal
// selector
if (logger.isDebugEnabled())
logger
.debug("element not found but terminal selector exists, returning no parent conflict with document as ancestor");
throw new NoParentConflictException(
getElementExistentAncestor(
xcapRoot,
resourceSelector
.getDocumentSelector(),
elementSelectorWithEmptyPrefix,
domDocument, xpath));
} else { // NEW ELEMENT
if (mimetype == null
|| !mimetype
.equals(ElementResource.MIMETYPE)) {
// mimetype is not correct
throw new UnsupportedMediaTypeException();
}
try {
// parse the element selector
ElementSelector elementSelector = Parser
.parseElementSelector(nodeSelector
.getElementSelector());
if (logger.isDebugEnabled())
logger
.debug("element selector parsed with sucess");
// get element parent selector with
// empty prefix
String elementParentSelectorWithEmptyPrefix = nodeSelector
.getElementParentSelectorWithEmptyPrefix();
boolean elementParentExists = true;
try {
// get element parent
NodeList parentElementNodeList = (NodeList) xpath
.evaluate(
elementParentSelectorWithEmptyPrefix,
domDocument,
XPathConstants.NODESET);
if (parentElementNodeList
.getLength() == 1
&& parentElementNodeList
.item(0) instanceof Element) { // ELEMENT
// PARENT
// EXISTS
Element elementParent = (Element) parentElementNodeList
.item(0);
// read and verify if content
// value is utf-8
newElementAsString = XMLValidator
.getUTF8String(contentStream);
if (logger.isDebugEnabled())
logger
.debug("element content is utf-8");
// create XML fragment node
newElement = XMLValidator
.getWellFormedDocumentFragment(new StringReader(
newElementAsString));
if (logger.isDebugEnabled())
logger
.debug("element content is well formed document fragment");
newElement = (Element) domDocument
.importNode(newElement,
true);
// put new element
domDocument = putNewElementInDocument(
newElement,
elementSelector,
elementParent,
domDocument, nsContext);
if (logger.isDebugEnabled())
logger
.debug("element parent found, new element added");
} else {
elementParentExists = false;
}
} catch (XPathExpressionException e) {
// invalid xpath expression
elementParentExists = false;
}
if (!elementParentExists) { // ELEMENT
// PARENT
// DOES NOT
// EXIST
// find it's existing ancestor &
// throw exception
if (logger.isDebugEnabled())
logger
.debug("element parent not found, returning no parent conflict");
throw new NoParentConflictException(
getElementExistentAncestor(
xcapRoot,
resourceSelector
.getDocumentSelector(),
elementParentSelectorWithEmptyPrefix,
domDocument, xpath));
}
} catch (ParseException e) {
// failed to parse the element selector,
// throw no parent exception
if (logger.isDebugEnabled())
logger
.debug("failed to parse element selector, returning no parent conflict");
throw new NoParentConflictException(
getElementExistentAncestor(
xcapRoot,
resourceSelector
.getDocumentSelector(),
elementSelectorWithEmptyPrefix,
domDocument, xpath));
}
// create result
result = new CreatedWriteResult();
}
}
} catch (XPathExpressionException e) {
// invalid xpath expression
if (logger.isInfoEnabled()) {
logger.info("error in xpath expression");
}
// check element selector for unbinded prefixes
if (nodeSelector
.elementSelectorHasUnbindedPrefixes(namespaces)) {
if (logger.isDebugEnabled())
logger
.debug("element selector doesn't have prefixe(s) bound, bad request");
throw new BadRequestException();
} else {
// find existing ancestor
if (logger.isDebugEnabled())
logger
.debug("element not found, returning no parent conflict");
throw new NoParentConflictException(
getElementExistentAncestor(xcapRoot,
resourceSelector
.getDocumentSelector(),
elementSelectorWithEmptyPrefix,
domDocument, xpath));
}
}
} catch (ParseException e) {
// unable to parse the node selector, throw no parent
// exception with the document as the existent ancestor
if (logger.isDebugEnabled())
logger
.debug("unable to parse the node selector, returning no parent conflict with the document as the existent ancestor");
throw new NoParentConflictException(xcapRoot
+ resourceSelector.getDocumentSelector());
}
} else { // DOCUMENT EXISTS, REPLACE IT
if (logger.isDebugEnabled())
logger.debug("document found");
if (mimetype == null
|| !mimetype.equals(appUsage.getMimetype())) {
// mimetype is not valid
if (logger.isDebugEnabled())
logger
.debug("invalid mimetype, does not matches the app usage");
throw new UnsupportedMediaTypeException();
}
// verify if content is utf-8
Reader utf8reader = XMLValidator
.getUTF8Reader(contentStream);
if (logger.isDebugEnabled())
logger.debug("document content is utf-8");
// get document
domDocument = XMLValidator
.getWellFormedDocument(utf8reader);
if (logger.isDebugEnabled())
logger.debug("document content is well formed");
// create result
result = new OKWriteResult();
}
}
// validate the updated document against it's schema
appUsage.validateSchema(domDocument);
if (logger.isDebugEnabled())
logger.debug("document validated by schema");
// verify app usage constraints
appUsage.checkConstraintsOnPut(domDocument, xcapRoot,
documentSelector, dataSourceSbbInterface);
if (logger.isDebugEnabled())
logger.debug("app usage constraints checked");
// process resource interdependencies
appUsage.processResourceInterdependenciesOnPut(domDocument,
documentSelector, dataSourceSbbInterface);
if (logger.isDebugEnabled())
logger.debug("app usage resource interdependencies processed");
// create new document etag
String newETag = ETagGenerator.generate(resourceSelector
.getDocumentSelector());
if (logger.isDebugEnabled())
logger
.debug("new document etag generated and stored in data source");
// update data source with document
try {
String xml = TextWriter.toString(domDocument);
if (document == null) {
dataSourceSbbInterface.createDocument(documentSelector,
newETag, xml, domDocument);
if (logger.isDebugEnabled())
logger.debug("document created in data source");
} else {
if (attributeSelector != null) {
// attribute update
dataSourceSbbInterface.updateAttribute(
documentSelector, nodeSelector,
attributeSelector, namespaces, document
.getETag(), newETag, xml, domDocument,
attributeValue);
} else if (nodeSelector != null) {
// element update
dataSourceSbbInterface.updateElement(documentSelector,
nodeSelector, namespaces, document.getETag(),
newETag, xml, domDocument, newElementAsString,
newElement);
} else {
// whole doc
dataSourceSbbInterface.updateDocument(documentSelector,
document.getETag(), newETag, xml, domDocument);
}
if (logger.isDebugEnabled())
logger.debug("document updated in data source");
}
} catch (Exception e) {
logger.error(e);
throw new InternalServerErrorException(
"Failed to serialize resulting dom document to string");
}
// add it to the result
result.setResponseEntityTag(newETag);
// and return that result
return result;
} catch (ParseException e) {
// invalid document selector, throw no parent exception
if (logger.isDebugEnabled())
logger
.debug("failed to parse document selector, returning no parent conflict");
throw new NoParentConflictException(getDocumentExistingAncestor(
xcapRoot, documentSelector != null ? documentSelector.getAUID() : null,
documentSelector != null ? documentSelector
.getDocumentParent() : e.getValidParent(),
dataSourceSbbInterface));
} catch (InterruptedException e) {
String msg = "failed to borrow app usage object from cache";
logger.error(msg, e);
throw new InternalServerErrorException(msg);
} finally {
if (appUsage != null) {
appUsageCache.release(appUsage);
}
}
}
private String getDocumentExistingAncestor(String xcapRoot, String auid,
String documentParent, DataSource dataSource)
throws InternalServerErrorException {
StringBuilder sb = new StringBuilder(xcapRoot).append('/').append(auid);
String existingDocumentParent = null;
if (documentParent != null) {
if (documentParent.startsWith("/" + auid + "/")) {
existingDocumentParent = dataSource.getExistingCollection(auid,
documentParent.substring(3 + auid.length()));
} else {
existingDocumentParent = dataSource.getExistingCollection(auid,
documentParent);
}
}
return existingDocumentParent != null ? sb.append('/').append(
existingDocumentParent).toString() : sb.toString();
}
private String getElementExistentAncestor(String xcapRoot,
String documentSelector, String elementSelectorWithEmptyPrefix,
Document document, XPath xpath) {
// first part is the xcap uri that points to the document
StringBuilder sb = new StringBuilder(xcapRoot).append(documentSelector);
// loop till we find an existing ancestor
String elementAncestor = null;
int index = -1;
while ((index = elementSelectorWithEmptyPrefix.lastIndexOf('/')) > 0) {
elementSelectorWithEmptyPrefix = elementSelectorWithEmptyPrefix
.substring(0, index);
try {
Element element = (Element) xpath.evaluate(
elementSelectorWithEmptyPrefix, document,
XPathConstants.NODE);
if (element != null) {
elementAncestor = elementSelectorWithEmptyPrefix;
break;
}
} catch (XPathExpressionException e) {
// silently ignore an invalid xpath expression, specs requires
// it
}
}
if (elementAncestor != null) {
// existing element ancestor found
// remove empty prefixes if those exist
elementAncestor = elementAncestor.replaceAll("/:", "/");
// and add it to the ancestor
sb.append("/~~").append(elementAncestor);
}
String ancestor = sb.toString();
if (logger.isDebugEnabled())
logger.debug("existing ancestor is " + ancestor);
return ancestor;
}
private Document putNewElementInDocument(Element newElement,
ElementSelector elementSelector, Element elementParent,
Document domDocument, NamespaceContext namespaceContext)
throws InternalServerErrorException, CannotInsertConflictException,
NotValidXMLFragmentConflictException, NotUTF8ConflictException {
// get element step
ElementSelectorStep elementLastStep = elementSelector.getLastStep();
// get element name & namespace
String elementNamespace = null;
String elementName = null;
String elementNamePrefix = elementLastStep.getPrefix();
if (elementNamePrefix != null) {
// get element name without prefix
elementName = elementLastStep.getNameWithoutPrefix();
// and get namespace
elementNamespace = namespaceContext
.getNamespaceURI(elementNamePrefix);
} else {
// get element name without prefix
elementName = elementLastStep.getName();
// and get namespace
elementNamespace = namespaceContext.getNamespaceURI("");
}
// if new element node name is not the same as in the uri then cannot
// insert
if (!newElement.getNodeName().equals(elementName)) {
if (logger.isDebugEnabled())
logger
.debug("element node name is not the same as in the uri, cannot insert");
throw new CannotInsertConflictException();
}
if (elementLastStep instanceof ElementSelectorStepByPos) {
// position defined
if (logger.isDebugEnabled())
logger
.debug("element selector's last step with position defined");
ElementSelectorStepByPos elementSelectorStepByPos = (ElementSelectorStepByPos) elementLastStep;
if (elementSelectorStepByPos.getPos() == 1) {
// POS = 1
if (!(elementLastStep instanceof ElementSelectorStepByPosAttr)) {
// NO ATTR TEST, *[1] e name[1], either way, just append to
// the parent
if (logger.isDebugEnabled())
logger
.debug("element selector's last step without attr test defined");
elementParent.appendChild(newElement);
if (logger.isDebugEnabled())
logger.debug("element appended to parent");
} else {
// ATTR TEST
if (logger.isDebugEnabled())
logger
.debug("element selector's last step with attr test defined");
// verify that the element has this step atribute with this
// step attribute value, if not it cannot insert
ElementSelectorStepByPosAttr elementSelectorStepByPosAttr = (ElementSelectorStepByPosAttr) elementLastStep;
String elementAttrName = elementSelectorStepByPosAttr
.getAttrName();
String elementAttrValue = newElement
.getAttribute(elementAttrName);
if (elementAttrValue == null
|| !elementAttrValue
.equals(elementSelectorStepByPosAttr
.getAttrValue())) {
if (logger.isDebugEnabled())
logger
.debug("element selector's last step has an atribute and the attribute value does not matches, cannot insert");
throw new CannotInsertConflictException();
}
// *[1][attr-test], insert before the first element
// name[1][attr-test], insert before the first element with
// same name
NodeList elementParentChilds = elementParent
.getChildNodes();
boolean inserted = false;
for (int i = 0; i < elementParentChilds.getLength(); i++) {
if (elementParentChilds.item(i) instanceof Element
&& ((elementName.equals(elementParentChilds
.item(i).getNodeName()) && elementParentChilds
.item(i).getNamespaceURI().equals(
elementNamespace)) || (elementName
.equals("*")))) {
elementParent.insertBefore(newElement,
elementParentChilds.item(i));
if (logger.isDebugEnabled())
logger.debug("element inserted at pos " + i);
inserted = true;
break;
}
}
if (!inserted) {
// didn't found an element just append to parent
elementParent.appendChild(newElement);
if (logger.isDebugEnabled())
logger.debug("element appended to parent");
}
}
}
else {
// POS > 1, must find the pos-1 element and insert after
if (elementLastStep instanceof ElementSelectorStepByPosAttr) {
// ATTR TEST
if (logger.isDebugEnabled())
logger
.debug("element selector's last step with attr test defined");
// verify that the element has this step atribute with this
// step attribute value, if not it cannot insert
ElementSelectorStepByPosAttr elementSelectorStepByPosAttr = (ElementSelectorStepByPosAttr) elementLastStep;
String elementAttrName = elementSelectorStepByPosAttr
.getAttrName();
String elementAttrValue = newElement
.getAttribute(elementAttrName);
if (elementAttrValue == null
|| !elementAttrValue
.equals(elementSelectorStepByPosAttr
.getAttrValue())) {
if (logger.isDebugEnabled())
logger
.debug("element selector's last step has an atribute and the attribute value does not matches, cannot insert");
throw new CannotInsertConflictException();
}
}
// *[pos>1], name[pos>1], *[pos>1][attr-test],
// name[pos>1][attr-test], insert in the parent after the pos-1
// element
NodeList elementParentChilds = elementParent.getChildNodes();
boolean inserted = false;
int elementsFound = 0;
for (int i = 0; i < elementParentChilds.getLength(); i++) {
if (elementParentChilds.item(i) instanceof Element
&& ((elementName.equals(elementParentChilds.item(i)
.getNodeName()) && elementParentChilds
.item(i).getNamespaceURI().equals(
elementNamespace)) || (elementName
.equals("*")))) {
elementsFound++;
if (elementsFound == elementSelectorStepByPos.getPos() - 1) {
// insert after
if (i == elementParentChilds.getLength() - 1) {
// no node after, use append
elementParent.appendChild(newElement);
if (logger.isDebugEnabled())
logger.debug("element appended to parent");
} else {
// node after exists, insert before
elementParent.insertBefore(newElement,
elementParentChilds.item(i + 1));
if (logger.isDebugEnabled())
logger.debug("element inserted at pos " + i
+ 1);
}
inserted = true;
break;
}
}
}
if (!inserted) {
// didn't found pos-1 element, cannot insert
if (logger.isDebugEnabled())
logger.debug("didn't found "
+ (elementSelectorStepByPos.getPos() - 1)
+ " element, cannot insert");
throw new CannotInsertConflictException();
}
}
}
else if (elementLastStep instanceof ElementSelectorStepByAttr) {
// no position defined
if (logger.isDebugEnabled())
logger
.debug("element selector's last step with attr test defined only");
// first verify element has this step atribute with this step
// attribute value, if not it cannot insert
ElementSelectorStepByAttr elementSelectorStepByAttr = (ElementSelectorStepByAttr) elementLastStep;
String elementAttrValue = newElement
.getAttribute(elementSelectorStepByAttr.getAttrName());
if (elementAttrValue == null
|| !elementAttrValue.equals(elementSelectorStepByAttr
.getAttrValue())) {
if (logger.isDebugEnabled())
logger
.debug("element selector's last step has an atribute and the attribute value does not matches, cannot insert");
throw new CannotInsertConflictException();
}
// insert after the last with same name
NodeList elementParentChilds = elementParent.getChildNodes();
boolean inserted = false;
for (int i = elementParentChilds.getLength() - 1; i > -1; i--) {
if (elementParentChilds.item(i) instanceof Element) {
if (elementParentChilds.item(i) instanceof Element
&& ((elementName.equals(elementParentChilds.item(i)
.getNodeName()) && elementParentChilds
.item(i).getNamespaceURI().equals(
elementNamespace)) || (elementName
.equals("*")))) {
// insert after this element
if (i == elementParentChilds.getLength() - 1) {
elementParent.appendChild(newElement);
if (logger.isDebugEnabled())
logger.debug("element appended to parent");
} else {
elementParent.insertBefore(newElement,
elementParentChilds.item(i + 1));
if (logger.isDebugEnabled())
logger
.debug("element inserted at pos " + i
+ 1);
}
inserted = true;
break;
}
}
}
if (!inserted) {
// didn't found an element with same name and namespace, just
// append to parent
elementParent.appendChild(newElement);
if (logger.isDebugEnabled())
logger.debug("element appended to parent");
}
}
else {
// no position and attr defined, it's the first child or the first
// with this name so just append new element
elementParent.appendChild(newElement);
if (logger.isDebugEnabled())
logger
.debug("element selector's last step without attr test or position defined, element appended to parent");
}
return domDocument;
}
}