/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.cxf.ws.transfer.dialect.fragment; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import javax.annotation.Resource; import javax.xml.bind.JAXBElement; import javax.xml.ws.WebServiceContext; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.apache.cxf.binding.soap.SoapFault; import org.apache.cxf.binding.soap.SoapMessage; import org.apache.cxf.binding.soap.SoapVersion; import org.apache.cxf.helpers.DOMUtils; import org.apache.cxf.jaxws.context.WrappedMessageContext; import org.apache.cxf.ws.transfer.Create; import org.apache.cxf.ws.transfer.Delete; import org.apache.cxf.ws.transfer.Get; import org.apache.cxf.ws.transfer.Put; import org.apache.cxf.ws.transfer.Representation; import org.apache.cxf.ws.transfer.dialect.Dialect; import org.apache.cxf.ws.transfer.dialect.fragment.faults.InvalidExpression; import org.apache.cxf.ws.transfer.dialect.fragment.faults.UnsupportedLanguage; import org.apache.cxf.ws.transfer.dialect.fragment.faults.UnsupportedMode; import org.apache.cxf.ws.transfer.dialect.fragment.language.FragmentDialectLanguage; import org.apache.cxf.ws.transfer.dialect.fragment.language.FragmentDialectLanguageQName; import org.apache.cxf.ws.transfer.dialect.fragment.language.FragmentDialectLanguageXPath10; import org.apache.cxf.ws.transfer.shared.faults.InvalidRepresentation; import org.apache.cxf.ws.transfer.shared.faults.UnknownDialect; /** * Implementation of the WS-Fragment dialect. */ public class FragmentDialect implements Dialect { @Resource WebServiceContext context; private final Map<String, FragmentDialectLanguage> languages; private Pattern badXPathPattern; private Pattern goodXPathPattern; public FragmentDialect() { languages = new HashMap<>(); languages.put(FragmentDialectConstants.QNAME_LANGUAGE_IRI, new FragmentDialectLanguageQName()); languages.put(FragmentDialectConstants.XPATH10_LANGUAGE_IRI, new FragmentDialectLanguageXPath10()); if (badXPathPattern == null) { StringBuilder sb = new StringBuilder(); sb.append("//@?"); sb.append(FragmentDialectLanguageQName.getQNamePatternString()); sb.append("$"); badXPathPattern = Pattern.compile(sb.toString()); } if (goodXPathPattern == null) { StringBuilder sb = new StringBuilder(); sb.append("/@?"); sb.append(FragmentDialectLanguageQName.getQNamePatternString()); sb.append("$"); goodXPathPattern = Pattern.compile(sb.toString()); } } @Override public JAXBElement<ValueType> processGet(Get body, Representation representation) { for (Object o : body.getAny()) { if (o instanceof JAXBElement<?> && ((JAXBElement<?>)o).getDeclaredType() == ExpressionType.class) { ExpressionType expression = (ExpressionType) ((JAXBElement<?>)o).getValue(); String languageIRI = expression.getLanguage(); languageIRI = languageIRI == null ? FragmentDialectConstants.XPATH10_LANGUAGE_IRI : languageIRI; if (languages.containsKey(languageIRI)) { FragmentDialectLanguage language = languages.get(languageIRI); return generateGetResponse(language.getResourceFragment(representation, expression)); } else { throw new UnsupportedLanguage(); } } } throw new SoapFault("wsf:Expression is not present.", getSoapVersion().getSender()); } @Override public Representation processPut(Put body, Representation representation) { for (Object o : body.getAny()) { if (o instanceof Fragment) { Fragment fragment = (Fragment) o; ExpressionType expression = fragment.getExpression(); ValueType value = fragment.getValue(); if (expression == null) { throw new SoapFault("wsf:Expression is not present.", getSoapVersion().getSender()); } if (value == null) { value = new ValueType(); } String languageIRI = expression.getLanguage(); languageIRI = languageIRI == null ? FragmentDialectConstants.XPATH10_LANGUAGE_IRI : languageIRI; if (languages.containsKey(languageIRI)) { FragmentDialectLanguage language = languages.get(languageIRI); Object resourceFragment = language.getResourceFragment(representation, expression); String mode = expression.getMode(); mode = mode == null ? FragmentDialectConstants.FRAGMENT_MODE_REPLACE : mode; if (resourceFragment == null && FragmentDialectConstants.FRAGMENT_MODE_REPLACE.equals(mode) && FragmentDialectConstants.XPATH10_LANGUAGE_IRI.equals(languageIRI)) { resourceFragment = language.getResourceFragment(representation, getParentXPath(expression)); mode = FragmentDialectConstants.FRAGMENT_MODE_ADD; } return modifyRepresentation(resourceFragment, mode, value); } else { throw new UnsupportedLanguage(); } } } throw new SoapFault("wsf:Fragment is not present.", getSoapVersion().getSender()); } @Override public boolean processDelete(Delete body, Representation representation) { throw new UnknownDialect(); } @Override public Representation processCreate(Create body) { throw new UnknownDialect(); } /** * Register FragmentDialectLanguage object for IRI. * @param iri * @param language */ public void registerLanguage(String iri, FragmentDialectLanguage language) { if (languages.containsKey(iri)) { throw new IllegalArgumentException(String.format("IRI \"%s\" is already registered", iri)); } languages.put(iri, language); } /** * Unregister FragmentDialectLanguage object for IRI. * @param iri */ public void unregisterLanguage(String iri) { if (!languages.containsKey(iri)) { throw new IllegalArgumentException(String.format("IRI \"%s\" is not registered", iri)); } languages.remove(iri); } /** * Generates Value element, which is returned as response to Get request. * @param value Result of the XPath evaluation. * @return */ private JAXBElement<ValueType> generateGetResponse(Object value) { if (value instanceof Node) { return generateGetResponseNode((Node) value); } else if (value instanceof NodeList) { return generateGetResponseNodeList((NodeList) value); } else if (value instanceof String) { return generateGetResponseString((String) value); } ObjectFactory objectFactory = new ObjectFactory(); return objectFactory.createValue(new ValueType()); } /** * Generates Value element from NodeList. * @param nodeList * @return */ private JAXBElement<ValueType> generateGetResponseNodeList(NodeList nodeList) { ValueType resultValue = new ValueType(); for (int i = 0; i < nodeList.getLength(); i++) { resultValue.getContent().add(nodeList.item(i)); } ObjectFactory objectFactory = new ObjectFactory(); return objectFactory.createValue(resultValue); } /** * Generates Value element from Node. * @param node * @return */ private JAXBElement<ValueType> generateGetResponseNode(Node node) { Document doc = DOMUtils.createDocument(); ValueType resultValue = new ValueType(); if (node.getNodeType() == Node.ATTRIBUTE_NODE) { Element attrNodeEl = doc.createElementNS( FragmentDialectConstants.FRAGMENT_2011_03_IRI, FragmentDialectConstants.FRAGMENT_ATTR_NODE_NAME); attrNodeEl.setAttribute( FragmentDialectConstants.FRAGMENT_ATTR_NODE_NAME_ATTR, node.getNodeName() ); attrNodeEl.setTextContent(node.getNodeValue()); resultValue.getContent().add(attrNodeEl); } else if (node.getNodeType() == Node.TEXT_NODE) { Element textNodeEl = doc.createElementNS( FragmentDialectConstants.FRAGMENT_2011_03_IRI, FragmentDialectConstants.FRAGMENT_TEXT_NODE_NAME); textNodeEl.setNodeValue(node.getNodeValue()); resultValue.getContent().add(textNodeEl); } else if (node.getNodeType() == Node.ELEMENT_NODE) { resultValue.getContent().add(node); } ObjectFactory objectFactory = new ObjectFactory(); return objectFactory.createValue(resultValue); } /** * Generates Value element from String. * @param value * @return */ private JAXBElement<ValueType> generateGetResponseString(String value) { ValueType resultValue = new ValueType(); resultValue.getContent().add(value); ObjectFactory objectFactory = new ObjectFactory(); return objectFactory.createValue(resultValue); } /** * Returns expression containing XPath, which refers to parent element. * @param expression * @return */ private ExpressionType getParentXPath(ExpressionType expression) { String expr; if (expression.getContent().size() == 1) { expr = (String) expression.getContent().get(0); } else { throw new InvalidExpression(); } if (badXPathPattern.matcher(expr).find()) { throw new InvalidExpression(); } if (goodXPathPattern.matcher(expr).find()) { expression.getContent().clear(); expr = expr.replaceFirst(goodXPathPattern.pattern(), ""); if (expr.isEmpty()) { expr = "/"; } expression.getContent().add(expr); return expression; } else { throw new InvalidExpression(); } } /** * Process Put requests. * @param resourceFragment Result of the XPath evaluation. It can be Node or NodeList. * @param mode Mode defined in the Mode attribute. * @param value Value defined in the Value element. * @return Representation element, which is returned as response. */ private Representation modifyRepresentation( Object resourceFragment, String mode, ValueType value) { if (resourceFragment instanceof Node) { List<Node> nodeList = new ArrayList<>(); nodeList.add((Node) resourceFragment); return modifyRepresentationMode(nodeList, mode, value); } else if (resourceFragment instanceof NodeList) { NodeList rfNodeList = (NodeList) resourceFragment; List<Node> nodeList = new ArrayList<>(); for (int i = 0; i < rfNodeList.getLength(); i++) { nodeList.add(rfNodeList.item(i)); } return modifyRepresentationMode(nodeList, mode, value); } else { throw new InvalidExpression(); } } /** * Process Put requests. * @param mode Mode defined in the Mode attribute. * @param value Value defined in the Value element. * @return Representation element, which is returned as response. */ private Representation modifyRepresentationMode( List<Node> nodeList, String mode, ValueType value) { switch (mode) { case FragmentDialectConstants.FRAGMENT_MODE_REPLACE: return modifyRepresentationModeReplace(nodeList, value); case FragmentDialectConstants.FRAGMENT_MODE_ADD: return modifyRepresentationModeAdd(nodeList, value); case FragmentDialectConstants.FRAGMENT_MODE_INSERT_BEFORE: return modifyRepresentationModeInsertBefore(nodeList, value); case FragmentDialectConstants.FRAGMENT_MODE_INSERT_AFTER: return modifyRepresentationModeInsertAfter(nodeList, value); case FragmentDialectConstants.FRAGMENT_MODE_REMOVE: return modifyRepresentationModeRemove(nodeList, value); default: throw new UnsupportedMode(); } } /** * Process Put requests for Replace mode. * @param value Value defined in the Value element. * @return Representation element, which is returned as response. */ private Representation modifyRepresentationModeReplace( List<Node> nodeList, ValueType value) { if (nodeList.isEmpty()) { throw new InvalidExpression(); } Node firstNode = nodeList.get(0); Document ownerDocument = firstNode.getOwnerDocument(); // if firstNode.getOwnerDocument == null the firstNode is ownerDocument ownerDocument = ownerDocument == null ? (Document) firstNode : ownerDocument; Node nextSibling = null; Node parent = null; for (Node node : nodeList) { nextSibling = node.getNextSibling(); parent = removeNode(node); } addNode(ownerDocument, parent, nextSibling, value); Representation representation = new Representation(); representation.setAny(ownerDocument.getDocumentElement()); return representation; } /** * Process Put requests for Add mode. * @param value Value defined in the Value element. * @return Representation element, which is returned as response. */ private Representation modifyRepresentationModeAdd( List<Node> nodeList, ValueType value) { if (nodeList.isEmpty()) { throw new InvalidExpression(); } Node firstNode = nodeList.get(0); Document ownerDocument = firstNode.getOwnerDocument(); // if firstNode.getOwnerDocument == null the firstNode is ownerDocument ownerDocument = ownerDocument == null ? (Document) firstNode : ownerDocument; for (Node node : nodeList) { addNode(ownerDocument, node, null, value); } Representation representation = new Representation(); representation.setAny(ownerDocument.getDocumentElement()); return representation; } /** * Process Put requests for InsertBefore mode. * @param value Value defined in the Value element. * @return Representation element, which is returned as response. */ private Representation modifyRepresentationModeInsertBefore( List<Node> nodeList, ValueType value) { if (nodeList.isEmpty()) { throw new InvalidExpression(); } Node firstNode = nodeList.get(0); Document ownerDocument = firstNode.getOwnerDocument(); // if firstNode.getOwnerDocument == null the firstNode is ownerDocument ownerDocument = ownerDocument == null ? (Document) firstNode : ownerDocument; Node parent = firstNode.getParentNode(); if (parent == null && firstNode.getNodeType() != Node.DOCUMENT_NODE) { throw new InvalidExpression(); } if (parent == null) { parent = firstNode; if (((Document) parent).getDocumentElement() != null) { throw new InvalidExpression(); } } for (Node node : nodeList) { if (node.getNodeType() == Node.ATTRIBUTE_NODE) { throw new InvalidRepresentation(); } insertBefore(ownerDocument, parent, node, value); } Representation representation = new Representation(); representation.setAny(ownerDocument.getDocumentElement()); return representation; } /** * Process Put requests for InsertAfter mode. * @param value Value defined in the Value element. * @return Representation element, which is returned as response. */ private Representation modifyRepresentationModeInsertAfter( List<Node> nodeList, ValueType value) { if (nodeList.isEmpty()) { throw new InvalidExpression(); } Node firstNode = nodeList.get(0); Document ownerDocument = firstNode.getOwnerDocument(); // if firstNode.getOwnerDocument == null the firstNode is ownerDocument ownerDocument = ownerDocument == null ? (Document) firstNode : ownerDocument; Node parent = firstNode.getParentNode(); if (parent == null && firstNode.getNodeType() != Node.DOCUMENT_NODE) { throw new InvalidExpression(); } if (parent == null) { parent = firstNode; if (((Document) parent).getDocumentElement() != null) { throw new InvalidExpression(); } } for (Node node : nodeList) { if (node.getNodeType() == Node.ATTRIBUTE_NODE) { throw new InvalidRepresentation(); } insertAfter(ownerDocument, parent, node, value); } Representation representation = new Representation(); representation.setAny(ownerDocument.getDocumentElement()); return representation; } /** * Process Put requests for Remove mode. * @param value Value defined in the Value element. * @return Representation element, which is returned as response. */ private Representation modifyRepresentationModeRemove( List<Node> nodeList, ValueType value) { if (nodeList.isEmpty()) { throw new InvalidExpression(); } Node firstNode = nodeList.get(0); Document ownerDocument = firstNode.getOwnerDocument(); // if firstNode.getOwnerDocument == null the firstNode is ownerDocument ownerDocument = ownerDocument == null ? (Document) firstNode : ownerDocument; for (Node node : nodeList) { removeNode(node); } Representation representation = new Representation(); representation.setAny(ownerDocument.getDocumentElement()); return representation; } /** * Helper method. It removes Node and returns its parent. * @param resourceFragment Node to remove. * @return Parent of removed Node. */ private Node removeNode(Node resourceFragment) { Node parent = null; if (resourceFragment.getNodeType() == Node.ATTRIBUTE_NODE) { parent = ((Attr)resourceFragment).getOwnerElement(); } else { parent = resourceFragment.getParentNode(); } if (parent == null) { // resourceFragment is Document Node parent = resourceFragment; } if (resourceFragment.getNodeType() == Node.ATTRIBUTE_NODE) { ((Element)parent).removeAttributeNode((Attr)resourceFragment); } else { if (parent != resourceFragment) { parent.removeChild(resourceFragment); } else { // Both parent and resourceFragment are Document Document doc = (Document) parent; if (doc.getDocumentElement() != null) { doc.removeChild(doc.getDocumentElement()); } } } return parent; } private void insertAfter(Document ownerDocument, Node parent, Node refChild, ValueType value) { for (Object o : value.getContent()) { if (o instanceof Node) { Node node = (Node) o; if ( FragmentDialectConstants.FRAGMENT_2011_03_IRI.equals(node.getNamespaceURI()) && FragmentDialectConstants.FRAGMENT_ATTR_NODE_NAME.equals(node.getLocalName())) { throw new InvalidRepresentation(); } Node importedNode = ownerDocument.importNode(node, true); if (parent.getNodeType() == Node.DOCUMENT_NODE) { parent.appendChild(importedNode); } else { Node nextSibling = refChild.getNextSibling(); if (nextSibling == null) { parent.appendChild(importedNode); } else { parent.insertBefore(importedNode, nextSibling); } } } else { throw new InvalidExpression(); } } } private void insertBefore(Document ownerDocument, Node parent, Node refChild, ValueType value) { for (Object o : value.getContent()) { if (o instanceof Node) { Node node = (Node) o; if ( FragmentDialectConstants.FRAGMENT_2011_03_IRI.equals(node.getNamespaceURI()) && FragmentDialectConstants.FRAGMENT_ATTR_NODE_NAME.equals(node.getLocalName())) { throw new InvalidRepresentation(); } Node importedNode = ownerDocument.importNode(node, true); if (parent.getNodeType() == Node.DOCUMENT_NODE) { parent.appendChild(importedNode); } else { parent.insertBefore(importedNode, refChild); } } else { throw new InvalidExpression(); } } } /** * Helper method. It adds new Node as the last child of parent. * @param ownerDocument Document, where the Node is added. * @param parent Parent, where the Node is added. * @param value Value defined in the Value element. It represents newly added Node. */ private void addNode(Document ownerDocument, Node parent, Node nextSibling, ValueType value) { if (ownerDocument == parent && ownerDocument.getDocumentElement() != null) { throw new InvalidRepresentation(); } for (Object o : value.getContent()) { if (o instanceof String) { parent.setTextContent(parent.getTextContent() + ((String) o)); } else if (o instanceof Node) { Node node = (Node) o; if ( FragmentDialectConstants.FRAGMENT_2011_03_IRI.equals(node.getNamespaceURI()) && FragmentDialectConstants.FRAGMENT_ATTR_NODE_NAME.equals(node.getLocalName())) { String attrName = ((Element)node).getAttributeNS( FragmentDialectConstants.FRAGMENT_2011_03_IRI, FragmentDialectConstants.FRAGMENT_ATTR_NODE_NAME_ATTR); String attrValue = node.getTextContent(); if (attrName == null) { throw new SoapFault("wsf:AttributeNode@name is not present.", getSoapVersion().getSender()); } if (((Element) parent).hasAttribute(attrName)) { throw new InvalidRepresentation(); } ((Element) parent).setAttribute(attrName, attrValue); } else { // import the node to the ownerDocument Node importedNode = ownerDocument.importNode((Node) o, true); if (nextSibling == null) { parent.appendChild(importedNode); } else { parent.insertBefore(importedNode, nextSibling); } } } else { throw new InvalidExpression(); } } } private SoapVersion getSoapVersion() { WrappedMessageContext wmc = (WrappedMessageContext) context.getMessageContext(); SoapMessage message = (SoapMessage) wmc.getWrappedMessage(); return message.getVersion(); } }