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

com.flowlogix.util.ShrinkWrapManipulator Maven / Gradle / Ivy

/*
 * Copyright (C) 2011-2024 Flow Logix, Inc. All Rights Reserved.
 *
 * Licensed 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 com.flowlogix.util;

import java.io.InputStream;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.container.ClassContainer;
import org.jboss.shrinkwrap.api.container.ResourceContainer;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.shrinkwrap.resolver.api.maven.archive.importer.MavenImporter;
import org.omnifaces.util.Lazy;
import org.slf4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

/**
 * modifies xml files inside the archive according to xpath and function
 * 

* Examples: * {@snippet class="com.flowlogix.demo.util.ShrinkWrapDemo" region="productionMode"} * {@snippet class="com.flowlogix.demo.util.ShrinkWrapDemo" region="persistence"} * * @author lprimak */ @Slf4j public class ShrinkWrapManipulator { @RequiredArgsConstructor public static class Action { private final String path; private final Consumer func; private final boolean optional; public Action(String path, Consumer func) { this(path, func, false); } } static final String DEFAULT_SSL_PROPERTY = "sslPort"; static final int DEFAULT_SSL_PORT = 8181; final Lazy builder = new Lazy<>(this::createDocumentBuilder); final Lazy transformer = new Lazy<>(this::createTransformer); /** * Simple method to create ShrinkWrap (Arquillian archive from existing maven POM file * * @param archiveType * @return * @param ShrinkWrap archive type */ public static > TT createDeployment(Class archiveType) { return createDeployment(archiveType, name -> name); } /** * Simple method to create ShrinkWrap (Arquillian archive from existing maven POM file * * @param archiveType * @param nameTransformer transforms the UUID to a more suitable name * @return new archive * @param ShrinkWrap archive type */ public static > TT createDeployment(Class archiveType, UnaryOperator nameTransformer) { char firstLetter = archiveType.getSimpleName().toLowerCase().charAt(0); return createDeployment(archiveType, String.format("s%s.%car", nameTransformer.apply(UUID.randomUUID().toString()), firstLetter)); } /** * Simple method to create ShrinkWrap (Arquillian) archive from existing maven POM file * * @param archiveType * @param archiveName * @return new archive * @param ShrinkWrap archive type */ public static > TT createDeployment(Class archiveType, @NonNull String archiveName) { TT deployment = ShrinkWrap.create(MavenImporter.class, archiveName) .loadPomFromFile("pom.xml").importBuildOutput() .as(archiveType); if (deployment instanceof ClassContainer) { var containerDeployment = ((ClassContainer) deployment); optionalDeploymentOp(() -> containerDeployment .addClass("com.flowlogix.testcontainers.PayaraServerLifecycleExtension")); containerDeployment.addPackages(true, "org.assertj"); } return deployment; } /** * Logs the contents of the archive to the consumer * * @param archive to log * @param consumer to log the contents * @return the same archive * @param ShrinkWrap archive type */ public static > TT logArchiveContents(TT archive, Consumer consumer) { consumer.accept(archive.toString(true)); return archive; } /** * Adds payara-web.xml to the archive with the specified class delegation * * @param archive to modify * @param delegate whether to delegate or not * @return the same archive * @param ShrinkWrap archive type */ public static > TT payaraClassDelegation(TT archive, boolean delegate) { if (archive instanceof WebArchive webArchive) { webArchive.addAsWebInfResource( new StringAsset("" .formatted(delegate)), "payara-web.xml"); } else { getLogger().warn("Cannot add payara-web.xml to non-WebArchive"); } return archive; } /** * Adds SLF4J to the archive * * @param archive to modify * @return the same archive * @param ShrinkWrap archive type */ public static > TT packageSlf4j(TT archive) { payaraClassDelegation(archive, false); if (archive instanceof WebArchive webArchive) { String slf4jServiceProvider = "services/org.slf4j.spi.SLF4JServiceProvider"; webArchive.addPackages(true, Logger.class.getPackage()) .addAsWebInfResource(String.format("META-INF/%s", slf4jServiceProvider), String.format("classes/META-INF/%s", slf4jServiceProvider)); } else { getLogger().warn("Cannot add SLF4J to non-WebArchive"); } return archive; } /** * modifies web.xml according to xpath and method * * @param archive to modify * @param actions list of actions to perform */ public WebArchive webXmlXPath(WebArchive archive, List actions) { var asset = "WEB-INF/web.xml"; archive.setWebXML(new StringAsset(manipulateXml(archive, actions, asset))); return archive; } /** * modifies persistence.xml according to xpath and method * * @param archive to modify * @param actions list of actions to perform */ @SuppressWarnings("unchecked") public > Archive persistenceXmlXPath(Archive archive, List actions) { var asset = "META-INF/persistence.xml"; ((ResourceContainer) archive).addAsResource(new StringAsset(manipulateXml(archive, actions, "WEB-INF/classes/" + asset)), asset); return archive; } /** * Transform http to https URL using {@code sslPort} system property, * and default port 8181 if system property is not defined * * @param httpUrl http URL * @return https URL */ @SuppressWarnings("MagicNumber") public static URL toHttpsURL(URL httpUrl) { return toHttpsURL(httpUrl, DEFAULT_SSL_PROPERTY, DEFAULT_SSL_PORT); } /** * Transform http to https URL using the specified system property and default port, * if the system property is not defined * * @param httpUrl http URL * @param sslPortPropertyName * @param defaultPort * @return https URL */ @SneakyThrows({URISyntaxException.class, MalformedURLException.class}) public static URL toHttpsURL(URL httpUrl, String sslPortPropertyName, int defaultPort) { if (httpUrl.getProtocol().endsWith("s")) { return httpUrl; } int sslPort = Integer.getInteger(sslPortPropertyName, defaultPort); return new URI(httpUrl.getProtocol() + "s", null, httpUrl.getHost(), sslPort, httpUrl.getPath(), null, null).toURL(); } /** * Constructs XPath for web.xml context param * * @param paramName * @return XPath for web.xml context param */ public static String getContextParamValue(String paramName) { return String.format("//web-app/context-param[param-name = '%s']/param-value", paramName); } /** * Parse XML file from the archive, perform actions to modify the file, * and return a string representing the modified XML file * @param archive to retrieve the xml file from * @param actions to perform on the xml file * @param xmlFileName xml file name to retrive from the archive * @return string representation of the modified xml file */ @SneakyThrows public > String manipulateXml(Archive archive, List actions, String xmlFileName) { Document xmlDocument; try (InputStream strm = archive.get(xmlFileName).getAsset().openStream()) { xmlDocument = builder.get().parse(strm); } var xpath = XPathFactory.newInstance().newXPath(); for (Action action : actions) { var expr = xpath.compile(action.path); Node node = (Node) expr.evaluate(xmlDocument, XPathConstants.NODE); runActionOnNode(action, node); } StringWriter writer = new StringWriter(); transformer.get().transform(new DOMSource(xmlDocument), new StreamResult(writer)); return writer.getBuffer().toString(); } static void runActionOnNode(Action action, Node node) { if (node == null && action.optional) { log.debug("Optional path {} ignored", action.path); } else { action.func.accept(node); } } static Logger getLogger() { return log; } @SneakyThrows(ParserConfigurationException.class) private DocumentBuilder createDocumentBuilder() { return DocumentBuilderFactory.newInstance().newDocumentBuilder(); } @SneakyThrows(TransformerConfigurationException.class) private Transformer createTransformer() { return TransformerFactory.newInstance().newTransformer(); } private static void optionalDeploymentOp(Runnable operator) { try { operator.run(); } catch (Throwable e) { log.debug("Could not add optional class to deployment", e); } } }