content: /*
   * 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.camel.component.xslt.saxon;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.net.URI;
  import java.net.URISyntaxException;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import javax.xml.parsers.ParserConfigurationException;
  import javax.xml.parsers.SAXParser;
  import javax.xml.parsers.SAXParserFactory;
  import javax.xml.transform.Source;
  import javax.xml.transform.TransformerException;
  import javax.xml.transform.TransformerFactory;
  import javax.xml.transform.sax.SAXSource;
  import javax.xml.transform.stream.StreamSource;
  
  import org.xml.sax.InputSource;
  import org.xml.sax.SAXException;
  import org.xml.sax.XMLReader;
  
  import net.sf.saxon.Configuration;
  import net.sf.saxon.TransformerFactoryImpl;
  import org.apache.camel.CamelContext;
  import org.apache.camel.Category;
  import org.apache.camel.Component;
  import org.apache.camel.Exchange;
  import org.apache.camel.api.management.ManagedAttribute;
  import org.apache.camel.api.management.ManagedResource;
  import org.apache.camel.component.xslt.XsltBuilder;
  import org.apache.camel.component.xslt.XsltEndpoint;
  import org.apache.camel.spi.ClassResolver;
  import org.apache.camel.spi.Injector;
  import org.apache.camel.spi.Metadata;
  import org.apache.camel.spi.UriEndpoint;
  import org.apache.camel.spi.UriParam;
  import org.apache.camel.support.EndpointHelper;
  import org.apache.camel.support.ResourceHelper;
  import org.apache.camel.support.builder.ExpressionBuilder;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * Transform XML payloads using an XSLT template using Saxon.
   */
  @ManagedResource(description = "Managed XsltSaxonEndpoint")
  @UriEndpoint(firstVersion = "3.0.0", scheme = "xslt-saxon", title = "XSLT Saxon", syntax = "xslt-saxon:resourceUri",
               remote = false, producerOnly = true, category = { Category.CORE, Category.TRANSFORMATION })
  public class XsltSaxonEndpoint extends XsltEndpoint {
  
      private static final Logger LOG = LoggerFactory.getLogger(XsltSaxonEndpoint.class);
  
      @UriParam(label = "advanced")
      private Configuration saxonConfiguration;
      @Metadata(label = "advanced")
      private Map<String, Object> saxonConfigurationProperties = new HashMap<>();
      @Metadata(label = "advanced")
      private Map<String, Object> saxonReaderProperties = new HashMap<>();
      @UriParam(label = "advanced", javaType = "java.lang.String")
      private List<Object> saxonExtensionFunctions;
      @UriParam(displayName = "Allow StAX", defaultValue = "true")
      private boolean allowStAX = true;
      @UriParam(label = "advanced", defaultValue = "true")
      private boolean secureProcessing = true;
      @UriParam
      @Metadata(firstVersion = "4.15.0", displayName = "Use JSON Body", defaultValue = "false", required = false)
      private boolean useJsonBody = false;
  
      public XsltSaxonEndpoint(String endpointUri, Component component) {
          super(endpointUri, component);
      }
  
      @Override
      public boolean isRemote() {
          return false;
      }
  
      public List<Object> getSaxonExtensionFunctions() {
          return saxonExtensionFunctions;
      }
  
      /**
       * Allows you to use a custom net.sf.saxon.lib.ExtensionFunctionDefinition. You would need to add camel-saxon to the
       * classpath. The function is looked up in the registry, where you can comma to separate multiple values to lookup.
       */
      public void setSaxonExtensionFunctions(List<Object> extensionFunctions) {
          this.saxonExtensionFunctions = extensionFunctions;
      }
  
      /**
       * Allows you to use a custom net.sf.saxon.lib.ExtensionFunctionDefinition. You would need to add camel-saxon to the
       * classpath. The function is looked up in the registry, where you can comma to separate multiple values to lookup.
       */
      public void setSaxonExtensionFunctions(String extensionFunctions) {
          this.saxonExtensionFunctions = EndpointHelper.resolveReferenceListParameter(
                  getCamelContext(),
                  extensionFunctions,
                  Object.class);
      }
  
      public Configuration getSaxonConfiguration() {
          return saxonConfiguration;
      }
  
      /**
       * To use a custom Saxon configuration
       */
      public void setSaxonConfiguration(Configuration saxonConfiguration) {
          this.saxonConfiguration = saxonConfiguration;
      }
  
      public Map<String, Object> getSaxonConfigurationProperties() {
          return saxonConfigurationProperties;
      }
  
      /**
       * To set custom Saxon configuration properties
       */
      public void setSaxonConfigurationProperties(Map<String, Object> configurationProperties) {
          this.saxonConfigurationProperties = configurationProperties;
      }
  
      public Map<String, Object> getSaxonReaderProperties() {
          return saxonReaderProperties;
      }
  
      /**
       * To set custom Saxon Reader properties
       */
      public void setSaxonReaderProperties(Map<String, Object> saxonReaderProperties) {
          this.saxonReaderProperties = saxonReaderProperties;
      }
  
      @ManagedAttribute(description = "Whether to allow using StAX as the javax.xml.transform.Source")
      public boolean isAllowStAX() {
          return allowStAX;
      }
  
      /**
       * Whether to allow using StAX as the javax.xml.transform.Source. You can enable this if the XSLT library supports
       * StAX such as the Saxon library (camel-saxon). The Xalan library (default in JVM) does not support StAXSource.
       */
      public void setAllowStAX(boolean allowStAX) {
          this.allowStAX = allowStAX;
      }
  
      public boolean isSecureProcessing() {
          return secureProcessing;
      }
  
      /**
       * Feature for XML secure processing (see javax.xml.XMLConstants). This is enabled by default. However, when using
       * Saxon Professional you may need to turn this off to allow Saxon to be able to use Java extension functions.
       */
      public void setSecureProcessing(boolean secureProcessing) {
          this.secureProcessing = secureProcessing;
      }
  
      public boolean isUseJsonBody() {
          return useJsonBody;
      }
  
      /**
       * Whether to use JSON body as input. When enabled, the message body is expected to be JSON and will be converted to
       * XML representation of JSON using XSLT3 `json-to-xml()` function before XSLT processing. This allows XSLT
       * stylesheets to process JSON input directly using standard XPath expressions.
       */
      public void setUseJsonBody(boolean useJsonBody) {
          this.useJsonBody = useJsonBody;
      }
  
      @Override
      protected void doInit() throws Exception {
          super.doInit();
  
          // the processor is the xslt builder
          setXslt(createXsltBuilder());
  
          // must load resource first which sets a template and do a stylesheet compilation to catch errors early
          // load resource from classpath otherwise load in doStart()
          if (isContentCache() && ResourceHelper.isClasspathUri(getResourceUri())) {
              loadResource(getResourceUri(), getXslt());
          }
  
          setProcessor(getXslt());
      }
  
      @Override
      protected void doStart() throws Exception {
          super.doStart();
  
          if (isContentCache() && !ResourceHelper.isClasspathUri(getResourceUri())) {
              loadResource(getResourceUri(), getXslt());
          }
      }
  
      @Override
      protected XsltSaxonBuilder createXsltBuilder() throws Exception {
          final CamelContext ctx = getCamelContext();
          final ClassResolver resolver = ctx.getClassResolver();
          final Injector injector = ctx.getInjector();
  
          LOG.debug("{} using schema resource: {}", this, getResourceUri());
  
          final XsltSaxonBuilder xslt = injector.newInstance(XsltSaxonBuilder.class);
  
          TransformerFactory factory = getTransformerFactory();
          if (factory == null) {
              if (getTransformerFactoryClass() == null) {
                  // create new saxon factory
                  factory = new TransformerFactoryImpl();
              } else {
                  // provide the class loader of this component to work in OSGi environments
                  Class<TransformerFactory> factoryClass = resolver.resolveMandatoryClass(getTransformerFactoryClass(),
                          TransformerFactory.class, XsltSaxonComponent.class.getClassLoader());
                  LOG.debug("Using TransformerFactoryClass {}", factoryClass);
                  factory = injector.newInstance(factoryClass);
              }
          }
  
          if (factory instanceof TransformerFactoryImpl) {
              TransformerFactoryImpl tf = (TransformerFactoryImpl) factory;
              XsltSaxonHelper.registerSaxonConfiguration(tf, saxonConfiguration);
              XsltSaxonHelper.registerSaxonConfigurationProperties(tf, saxonConfigurationProperties);
              XsltSaxonHelper.registerSaxonExtensionFunctions(tf, saxonExtensionFunctions, secureProcessing);
          }
  
          if (factory != null) {
              LOG.debug("Using TransformerFactory {}", factory);
              xslt.setTransformerFactory(factory);
          }
          if (getResultHandlerFactory() != null) {
              xslt.setResultHandlerFactory(getResultHandlerFactory());
          }
          if (getErrorListener() != null) {
              xslt.errorListener(getErrorListener());
          }
          xslt.setFailOnNullBody(isFailOnNullBody());
          xslt.transformerCacheSize(getTransformerCacheSize());
          xslt.setUriResolver(getUriResolver());
          xslt.setEntityResolver(getEntityResolver());
          xslt.setAllowStAX(allowStAX);
          xslt.setUseJsonBody(useJsonBody);
          xslt.setDeleteOutputFile(isDeleteOutputFile());
          xslt.setSource(ExpressionBuilder.singleInputExpression(getSource()));
  
          if (getXsltMessageLogger() != null) {
              xslt.setXsltMessageLogger(getXsltMessageLogger());
          }
  
          configureOutput(xslt, getOutput().name());
  
          // any additional transformer parameters then make a copy to avoid side effects
          if (getParameters() != null) {
              Map<String, Object> copy = new HashMap<>(getParameters());
              xslt.setParameters(copy);
          }
  
          return xslt;
      }
  
      @Override
      protected XsltBuilder createBuilderForCustomStylesheet(String template, Exchange exchange) throws Exception {
          InputStream is = getCamelContext().getTypeConverter().mandatoryConvertTo(InputStream.class, exchange, template);
          XsltBuilder builder = createXsltBuilder();
          Source source = new StreamSource(is);
          if (this.saxonReaderProperties != null) {
              //for Saxon we need to create XMLReader for the coming source
              //so that the features configuration can take effect
              source = createReaderForSource(source);
          }
          builder.setTransformerSource(source);
          return builder;
      }
  
      /**
       * Loads the resource.
       *
       * @param  resourceUri          the resource to load
       * @throws TransformerException is thrown if error loading resource
       * @throws IOException          is thrown if error loading resource
       */
      @Override
      protected void loadResource(String resourceUri, XsltBuilder xslt) throws TransformerException, IOException {
          LOG.trace("{} loading schema resource: {}", this, resourceUri);
          Source source = xslt.getUriResolver().resolve(resourceUri, null);
          if (this.saxonReaderProperties != null) {
              //for Saxon we need to create XMLReader for the coming source
              //so that the features configuration can take effect
              source = createReaderForSource(source);
          }
          if (source == null) {
              throw new IOException("Cannot load schema resource " + resourceUri);
          } else {
              xslt.setTransformerSource(source);
          }
          // now loaded so clear flag
          setCacheCleared(false);
      }
  
      private Source createReaderForSource(Source source) {
          try {
              SAXParserFactory factory = SAXParserFactory.newInstance();
              SAXParser saxParser = factory.newSAXParser();
              XMLReader xmlReader = saxParser.getXMLReader();
  
              for (Map.Entry<String, Object> entry : this.saxonReaderProperties.entrySet()) {
                  String key = entry.getKey();
                  Object value = entry.getValue();
                  try {
                      URI uri = new URI(key);
                      if (value != null
                              && (value.toString().equals("true") || (value.toString().equals("false")))) {
                          xmlReader.setFeature(uri.toString(), Boolean.valueOf(value.toString()));
                      } else if (value != null) {
                          xmlReader.setProperty(uri.toString(), value);
                      }
                  } catch (URISyntaxException e) {
                      LOG.debug("{} isn't a valid URI, so ignore it", key);
                  }
              }
              InputSource inputSource = SAXSource.sourceToInputSource(source);
              return new SAXSource(xmlReader, inputSource);
          } catch (SAXException | ParserConfigurationException e) {
              LOG.info("Can't create XMLReader for source ", e);
              return null;
          }
      }
  
  }
  