All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.appclient.server.core.jws.CombinedXPath Maven / Gradle / Ivy

There is a newer version: 6.2024.6
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2014 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
// Portions Copyright [2019] Payara Foundation and/or affiliates

package org.glassfish.appclient.server.core.jws;

import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;

/**
 * XPath-based logic to combine generated JNLP elements with the developer-
 * provided JNLP.
 * 

* Three types of combinations: *

    *
  • owned - the generated content overrides the developer's (we "own" the content) *
  • merged - the generated content is merged in with the developer's content *
  • defaulted - the generated content is used only if no corresponding developer content exists *
*

* This is the abstract superclass for the various types of combinations of * generated and developer-provided elements. *

* The client-jnlp-config.properties file contains properties which define * which JNLP elements will be combined, one property for each of the types of * combinations supported. Each property's value is a comma-separated list of * this form: *

* parent-path:path-within-parent *

* Both the parent-path and the path-within-parent are valid XPath expressions. * We need to separate them like this because if the node is not present we * need to insert it into the parent, so we need the parent piece separate. * For example, the setting *

* /jnlp:/@codebase *

* refers to the codebase attribute within the jnlp element, while *

/jnlp/resources:/property

* refers to the property element within the resources element within the * jnlp element. * * @author tjquinn */ abstract class CombinedXPath { private static final Logger logger = Logger.getLogger(JavaWebStartInfo.APPCLIENT_SERVER_MAIN_LOGGER, JavaWebStartInfo.APPCLIENT_SERVER_LOGMESSAGE_RESOURCE); /** property names for the types of combined JNLP content */ private static final String OWNED_PROPERTY_NAME = "owned"; private static final String DEFAULTED_PROPERTY_NAME = "defaulted"; private static final String MERGED_PROPERTY_NAME = "merged"; private final static XPathFactory xPathFactory = XPathFactory.newInstance(); private static LSSerializer lsSerializer = null; private static LSOutput lsOutput = null; private final String parentPath; private final String targetRelativePath; /** xpath expression for the target node in the DOM for the developer's XML */ private final XPathExpression targetExpr; /** if developer didn't provide the target, this is the parent where we'll * create a new child. */ private final XPathExpression parentExpr; private enum Type { OWNED(OWNED_PROPERTY_NAME) { CombinedXPath combinedXPath(String pathA, String pathB) { return new OwnedXPath(xPathFactory.newXPath(), pathA, pathB); } }, DEFAULTED(DEFAULTED_PROPERTY_NAME) { CombinedXPath combinedXPath(String pathA, String pathB) { return new DefaultedXPath(xPathFactory.newXPath(), pathA, pathB); } }, MERGED(MERGED_PROPERTY_NAME) { CombinedXPath combinedXPath(String pathA, String pathB) { return new MergedXPath(xPathFactory.newXPath(), pathA, pathB); } }; private String propertyName; Type(final String propName) { propertyName = propName; } abstract CombinedXPath combinedXPath(String pathA, String pathB); } static List parse(final Properties p) { List result = new ArrayList<>(); result.addAll(CombinedXPath.parse(p, CombinedXPath.Type.DEFAULTED)); result.addAll(CombinedXPath.parse(p, CombinedXPath.Type.MERGED)); return result; } /** * For the given combination type fetch the corresponding property value * from the config properties and then parse it into the separate * parent:with-parent pairs, creating for each pair the correct type of * CombinedXPath object and returning a List of them. * @param p * @param type * @return */ private static List parse( final Properties p, Type type) { final List result = new ArrayList<>(); final String refs = p.getProperty(type.propertyName); for (String ref : refs.split(",")) { final String[] paths = ref.split(":"); if (paths.length != 2) { throw new IllegalArgumentException(ref); } result.add(type.combinedXPath(paths[0], paths[1])); } return result; } /** * Creates a new combined XPath. * * @param xPath XPath available for searching * @param parentPath path to parent for new child (if developer's document lacks the target) * @param targetRelativePath path relative to the parent for the target node in the developer DOM */ CombinedXPath( final XPath xPath, final String parentPath, final String targetRelativePath) { this.parentPath = parentPath; this.targetRelativePath = targetRelativePath; try { parentExpr = xPath.compile(parentPath); targetExpr = xPath.compile(parentPath + targetRelativePath); } catch (Exception e) { throw new RuntimeException(e); } } String parentPath() { return parentPath; } String targetRelativePath() { return targetRelativePath; } XPathExpression targetExpr() { return targetExpr; } XPathExpression parentExpr() { return parentExpr; } /** * Processes the given combination: replaces, defaults, or merges. *

* Note that the template - which takes the form at this point of the * generatedDOM - contains some text nodes that are important. So we * start with the generatedDOM as the "master" and then combine the * developer content into it. * * @param developerDOM * @param generatedDOM * @throws XPathExpressionException */ abstract void process(final Document developerDOM, final Document generatedDOM) throws XPathExpressionException; protected void insert( final Document originalDOM, final Node insertionPoint, final Node newNode) throws XPathExpressionException { if (newNode instanceof Attr) { setAttr(originalDOM, insertionPoint, (Attr) newNode); } else { insertNode(originalDOM, insertionPoint, newNode); } } private void setAttr( final Document originalDOM, final Node insertionPoint, final Attr newAttr) throws XPathExpressionException { final Element parent = (insertionPoint == null) ? (Element) parentExpr().evaluate(originalDOM, XPathConstants.NODE) : ((Attr) insertionPoint).getOwnerElement(); parent.setAttribute(newAttr.getName(), newAttr.getValue()); } private void insertNode( final Document originalDOM, final Node insertionPoint, final Node newNode) throws XPathExpressionException { final Node parentNode = (insertionPoint == null) ? (Node) parentExpr().evaluate(originalDOM, XPathConstants.NODE) : insertionPoint.getParentNode(); parentNode.insertBefore(originalDOM.adoptNode(newNode), insertionPoint); } /** * Represents a node in the document which we completely determine, * overriding any corresponding node from the developer's DOM. */ static class OwnedXPath extends CombinedXPath { OwnedXPath( final XPath xPath, final String parentPath, final String targetRelativePath) { super(xPath, parentPath, targetRelativePath); } @Override void process(Document developerDOM, Document generatedDOM) throws XPathExpressionException { } } /** * Represents a combination of the two XML documents resulting from * merging the two input documents. */ static class MergedXPath extends CombinedXPath { MergedXPath( final XPath xPath, final String parentPath, final String targetRelativePath) { super(xPath, parentPath, targetRelativePath); } @Override void process(Document developerDOM, Document generatedDOM) throws XPathExpressionException { NodeList developerNodes = (NodeList) targetExpr().evaluate(developerDOM, XPathConstants.NODESET); NodeList generatedNodes = (NodeList) targetExpr().evaluate(generatedDOM, XPathConstants.NODESET); final Node insertionPoint = (generatedNodes.getLength() > 0) ? generatedNodes.item(0) : null; final boolean isDetailed = logger.isLoggable(Level.FINER); for (int i = 0; i < developerNodes.getLength(); i++) { if (isDetailed) { logger.log(Level.FINER, "Inserting new node due to {0}:{1}", new Object[]{parentPath(), targetRelativePath()}); } insert(generatedDOM, insertionPoint, developerNodes.item(i)); if (isDetailed) { logger.log(Level.FINER, toXML(generatedDOM)); } } } } /** * Represents a combination in which the developer's setting is used if * present; otherwise the generated document's setting is used. */ static class DefaultedXPath extends CombinedXPath { DefaultedXPath( final XPath xPath, final String parentPath, final String targetRelativePath) { super(xPath, parentPath, targetRelativePath); } @Override void process(Document developerDOM, Document generatedDOM) throws XPathExpressionException { /* * The developer provided content for this XPath. So remove the * generated node(s) for this XPath and replace them with the * ones from the developer's content. */ final NodeList replacementNodes = (NodeList) targetExpr().evaluate(developerDOM, XPathConstants.NODESET); if (replacementNodes.getLength() == 0) { return; } final NodeList originalNodes = (NodeList) targetExpr().evaluate(generatedDOM, XPathConstants.NODESET); /* * Replace all the matching original children (if any) with the * replacement ones. */ final Node insertionPoint = (originalNodes.getLength() > 0) ? originalNodes.item(0).getPreviousSibling() : null; /* * Remove the old node first. They could be attributes and, if so, * we need to remove them first before setting them with the * replacement values. Otherwise, if we removed the old nodes * after setting the new ones, we could accidentally erase new * settings that were intended to replace old settings. */ final boolean isDetailed = logger.isLoggable(Level.FINER); for (int i = 0; i < originalNodes.getLength(); i++) { if (isDetailed) { logger.log(Level.FINER, "Removing generated node to make way for developer node based on {0}:{1}", new Object[] {parentPath(), targetRelativePath()}); } remove(originalNodes.item(i)); if (isDetailed) { logger.log(Level.FINER, toXML(generatedDOM)); } } for (int i = 0; i < replacementNodes.getLength(); i++) { insert(generatedDOM, insertionPoint, replacementNodes.item(i)); if (isDetailed) { logger.log(Level.FINER, toXML(generatedDOM)); } } } private void remove(final Node originalNode) { if (originalNode instanceof Attr) { removeAttr((Attr) originalNode); } else { removeNode(originalNode); } } private void removeNode(final Node originalNode) { originalNode.getParentNode().removeChild(originalNode); } private void removeAttr(final Attr originalAttr) { final Element parent = originalAttr.getOwnerElement(); parent.removeAttribute(originalAttr.getName()); } } private static String toXML(final Node node) { Writer writer = new StringWriter(); writeXML(node, writer); return writer.toString(); } private synchronized static void writeXML(final Node node, final Writer writer) { try { if (lsSerializer == null) { final DOMImplementation domImpl = DOMImplementationRegistry.newInstance(). getDOMImplementation(""); final DOMImplementationLS domLS = (DOMImplementationLS) domImpl.getFeature("LS", "3.0"); lsOutput = domLS.createLSOutput(); lsOutput.setEncoding("UTF-8"); lsSerializer = domLS.createLSSerializer(); } lsOutput.setCharacterStream(writer); lsSerializer.write(node, lsOutput); } catch (Exception ex) { throw new RuntimeException(ex); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy