cxf-core and cxf-rt-frontend-jaxrs changes since 3.4.3
diff --git a/tomee/tomee-plume-webapp/pom.xml b/tomee/tomee-plume-webapp/pom.xml
index 2e727e5..c49a4a1 100644
--- a/tomee/tomee-plume-webapp/pom.xml
+++ b/tomee/tomee-plume-webapp/pom.xml
@@ -551,10 +551,17 @@
<source>1.8</source>
<target>1.8</target>
<dependencies>
+ <dependency>org.apache.aries.blueprint:blueprint-parser:jar:1.6.0</dependency>
+ <dependency>org.apache.aries.blueprint:org.apache.aries.blueprint.api:jar:1.0.1</dependency>
+ <dependency>org.apache.aries.blueprint:org.apache.aries.blueprint.core:jar:1.10.2</dependency>
<dependency>org.apache.tomcat:tomcat-servlet-api:jar:10.0.4</dependency>
- <dependency>org.springframework:spring-core:jar:5.3.6</dependency>
- <dependency>org.springframework:spring-context:jar:5.3.6</dependency>
+ <dependency>org.osgi:org.osgi.core:jar:6.0.0</dependency>
+ <dependency>org.osgi:osgi.cmpn:jar:6.0.0</dependency>
+ <dependency>org.ow2.asm:asm:jar:9.1</dependency>
+ <dependency>org.springframework:spring-aop:jar:5.3.6</dependency>
<dependency>org.springframework:spring-beans:jar:5.3.6</dependency>
+ <dependency>org.springframework:spring-context:jar:5.3.6</dependency>
+ <dependency>org.springframework:spring-core:jar:5.3.6</dependency>
<dependency>org.springframework:spring-webmvc:jar:5.3.6</dependency>
</dependencies>
</configuration>
diff --git a/transform/src/patch/java/org/apache/cxf/annotations/Policy.java b/transform/src/patch/java/org/apache/cxf/annotations/Policy.java
new file mode 100644
index 0000000..a0d4a75
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/annotations/Policy.java
@@ -0,0 +1,80 @@
+/**
+ * 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.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Attaches a Policy to a service or method
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Inherited
+public @interface Policy {
+
+ String uri();
+
+ boolean includeInWSDL() default true;
+
+
+ /**
+ * The place to put the PolicyReference. The Default depends on the
+ * location of the annotation. On the method in the SEI, it would be
+ * the binding/operation, on the SEI, it would be the binding, on the
+ * service impl, the service element.
+ * @return location
+ */
+ Placement placement() default Placement.DEFAULT;
+
+ /**
+ * If Placement is PORT_TYPE_OPERATION_FAULT, or BINDING_OPERATION_FAULT,
+ * return the fault class associated with this documentation
+ * @return the fault class
+ */
+ Class<?> faultClass() default DEFAULT.class;
+
+ enum Placement {
+ DEFAULT,
+
+ PORT_TYPE,
+ PORT_TYPE_OPERATION,
+ PORT_TYPE_OPERATION_INPUT,
+ PORT_TYPE_OPERATION_OUTPUT,
+ PORT_TYPE_OPERATION_FAULT,
+
+ BINDING,
+ BINDING_OPERATION,
+ BINDING_OPERATION_INPUT,
+ BINDING_OPERATION_OUTPUT,
+ BINDING_OPERATION_FAULT,
+
+ SERVICE,
+ SERVICE_PORT,
+ }
+
+ final class DEFAULT { }
+}
+
diff --git a/transform/src/patch/java/org/apache/cxf/annotations/WSDLDocumentation.java b/transform/src/patch/java/org/apache/cxf/annotations/WSDLDocumentation.java
new file mode 100644
index 0000000..9116f1c
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/annotations/WSDLDocumentation.java
@@ -0,0 +1,88 @@
+/**
+ * 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.annotations;
+
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Adds documentation nodes to the generated WSDL
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Inherited
+public @interface WSDLDocumentation {
+ /**
+ * The documentation to add
+ * @return documentation string
+ */
+ String value();
+
+ /**
+ * The place to put the documentation. The Default depends on the
+ * location of the annotation. On the method in the SEI, it would be
+ * the portType/operation, on the SEI, it would be the portType, on the
+ * service impl, the service element.
+ * @return location
+ */
+ Placement placement() default Placement.DEFAULT;
+
+ /**
+ * If Placement is FAULT_MESSAGE, PORT_FAULT, or BINDING_FAULT,
+ * return the fault class associated with this documentation
+ * @return the fault class
+ */
+ Class<?> faultClass() default DEFAULT.class;
+
+ enum Placement {
+ DEFAULT,
+ TOP,
+
+ INPUT_MESSAGE,
+ OUTPUT_MESSAGE,
+ FAULT_MESSAGE,
+
+ PORT_TYPE,
+ PORT_TYPE_OPERATION,
+ PORT_TYPE_OPERATION_INPUT,
+ PORT_TYPE_OPERATION_OUTPUT,
+ PORT_TYPE_OPERATION_FAULT,
+
+ BINDING,
+ BINDING_OPERATION,
+ BINDING_OPERATION_INPUT,
+ BINDING_OPERATION_OUTPUT,
+ BINDING_OPERATION_FAULT,
+
+ SERVICE,
+ SERVICE_PORT,
+
+ }
+
+
+ final class DEFAULT { }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/attachment/AttachmentDataSource.java b/transform/src/patch/java/org/apache/cxf/attachment/AttachmentDataSource.java
new file mode 100644
index 0000000..6e4fa1a
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/attachment/AttachmentDataSource.java
@@ -0,0 +1,107 @@
+/**
+ * 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.attachment;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.activation.DataSource;
+
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.io.CacheSizeExceededException;
+import org.apache.cxf.io.CachedOutputStream;
+import org.apache.cxf.message.Message;
+
+public class AttachmentDataSource implements DataSource {
+
+ private final String ct;
+ private CachedOutputStream cache;
+ private InputStream ins;
+ private DelegatingInputStream delegate;
+ private String name;
+
+ public AttachmentDataSource(String ctParam, InputStream inParam) {
+ this.ct = ctParam;
+ ins = inParam;
+ }
+
+ public boolean isCached() {
+ return cache != null;
+ }
+ public void cache(Message message) throws IOException {
+ if (cache == null) {
+ cache = new CachedOutputStream();
+ AttachmentUtil.setStreamedAttachmentProperties(message, cache);
+ try {
+ IOUtils.copyAndCloseInput(ins, cache);
+ cache.lockOutputStream();
+ if (delegate != null) {
+ delegate.setInputStream(cache.getInputStream());
+ }
+ } catch (CacheSizeExceededException | IOException cee) {
+ cache.close();
+ cache = null;
+ throw cee;
+ } finally {
+ ins = null;
+ }
+ }
+ }
+ public void hold(Message message) throws IOException {
+ cache(message);
+ cache.holdTempFile();
+ }
+ public void release() {
+ if (cache != null) {
+ cache.releaseTempFileHold();
+ }
+ }
+
+ public String getContentType() {
+ return ct;
+ }
+
+ public InputStream getInputStream() {
+ try {
+ if (cache != null) {
+ return cache.getInputStream();
+ }
+ if (delegate == null) {
+ delegate = new DelegatingInputStream(ins);
+ }
+ return delegate;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public OutputStream getOutputStream() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/attachment/AttachmentDeserializer.java b/transform/src/patch/java/org/apache/cxf/attachment/AttachmentDeserializer.java
new file mode 100644
index 0000000..55a1dcc
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/attachment/AttachmentDeserializer.java
@@ -0,0 +1,462 @@
+/**
+ * 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.attachment;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.activation.DataSource;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.common.util.SystemPropertyAction;
+import org.apache.cxf.helpers.HttpHeaderHelper;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.io.CachedOutputStream;
+import org.apache.cxf.message.Attachment;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageUtils;
+
+public class AttachmentDeserializer {
+ public static final String ATTACHMENT_PART_HEADERS = AttachmentDeserializer.class.getName() + ".headers";
+
+ /**
+ * Allowed value is any instance of {@link File} or {@link String}.
+ */
+ public static final String ATTACHMENT_DIRECTORY = "attachment-directory";
+
+ /**
+ * The memory threshold of attachments. Allowed value is any instance of {@link Number} or {@link String}.
+ * The default is {@link AttachmentDeserializer#THRESHOLD}.
+ */
+ public static final String ATTACHMENT_MEMORY_THRESHOLD = "attachment-memory-threshold";
+
+ /**
+ * The maximum size of the attachment. Allowed value is any of {@link Number} or {@link String}.
+ */
+ public static final String ATTACHMENT_MAX_SIZE = "attachment-max-size";
+
+ /**
+ * The maximum number of attachments permitted in a message. The default is 50.
+ */
+ public static final String ATTACHMENT_MAX_COUNT = "attachment-max-count";
+
+ /**
+ * The maximum MIME Header Length. The default is 300.
+ */
+ public static final String ATTACHMENT_MAX_HEADER_SIZE = "attachment-max-header-size";
+ public static final int DEFAULT_MAX_HEADER_SIZE =
+ SystemPropertyAction.getInteger("org.apache.cxf.attachment-max-header-size", 300);
+
+ public static final int THRESHOLD = 1024 * 100; //100K (byte unit)
+
+ private static final Pattern CONTENT_TYPE_BOUNDARY_PATTERN = Pattern.compile("boundary=\"?([^\";]*)");
+
+ private static final Pattern INPUT_STREAM_BOUNDARY_PATTERN =
+ Pattern.compile("^--(\\S*)$", Pattern.MULTILINE);
+
+ private static final Logger LOG = LogUtils.getL7dLogger(AttachmentDeserializer.class);
+
+ private static final int PUSHBACK_AMOUNT = 2048;
+
+ private boolean lazyLoading = true;
+
+ private PushbackInputStream stream;
+ private int createCount;
+ private int closedCount;
+ private boolean closed;
+
+ private byte[] boundary;
+
+ private LazyAttachmentCollection attachments;
+
+ private Message message;
+
+ private InputStream body;
+
+ private Set<DelegatingInputStream> loaded = new HashSet<>();
+ private List<String> supportedTypes;
+
+ private int maxHeaderLength = DEFAULT_MAX_HEADER_SIZE;
+
+ public AttachmentDeserializer(Message message) {
+ this(message, Collections.singletonList("multipart/related"));
+ }
+
+ public AttachmentDeserializer(Message message, List<String> supportedTypes) {
+ this.message = message;
+ this.supportedTypes = supportedTypes;
+
+ // Get the maximum Header length from configuration
+ maxHeaderLength = MessageUtils.getContextualInteger(message, ATTACHMENT_MAX_HEADER_SIZE,
+ DEFAULT_MAX_HEADER_SIZE);
+ }
+
+ public void initializeAttachments() throws IOException {
+ initializeRootMessage();
+
+ Object maxCountProperty = message.getContextualProperty(AttachmentDeserializer.ATTACHMENT_MAX_COUNT);
+ int maxCount = 50;
+ if (maxCountProperty != null) {
+ if (maxCountProperty instanceof Integer) {
+ maxCount = (Integer)maxCountProperty;
+ } else {
+ maxCount = Integer.parseInt((String)maxCountProperty);
+ }
+ }
+
+ attachments = new LazyAttachmentCollection(this, maxCount);
+ message.setAttachments(attachments);
+ }
+
+ protected void initializeRootMessage() throws IOException {
+ String contentType = (String) message.get(Message.CONTENT_TYPE);
+
+ if (contentType == null) {
+ throw new IllegalStateException("Content-Type can not be empty!");
+ }
+
+ if (message.getContent(InputStream.class) == null) {
+ throw new IllegalStateException("An InputStream must be provided!");
+ }
+
+ if (AttachmentUtil.isTypeSupported(contentType.toLowerCase(), supportedTypes)) {
+ String boundaryString = findBoundaryFromContentType(contentType);
+ if (null == boundaryString) {
+ boundaryString = findBoundaryFromInputStream();
+ }
+ // If a boundary still wasn't found, throw an exception
+ if (null == boundaryString) {
+ throw new IOException("Couldn't determine the boundary from the message!");
+ }
+ boundary = boundaryString.getBytes(StandardCharsets.UTF_8);
+
+ stream = new PushbackInputStream(message.getContent(InputStream.class), PUSHBACK_AMOUNT);
+ if (!readTillFirstBoundary(stream, boundary)) {
+ throw new IOException("Couldn't find MIME boundary: " + boundaryString);
+ }
+
+ Map<String, List<String>> ih = loadPartHeaders(stream);
+ message.put(ATTACHMENT_PART_HEADERS, ih);
+ String val = AttachmentUtil.getHeader(ih, "Content-Type", "; ");
+ if (!StringUtils.isEmpty(val)) {
+ String cs = HttpHeaderHelper.findCharset(val);
+ if (!StringUtils.isEmpty(cs)) {
+ message.put(Message.ENCODING, HttpHeaderHelper.mapCharset(cs));
+ }
+ }
+ val = AttachmentUtil.getHeader(ih, "Content-Transfer-Encoding");
+
+ MimeBodyPartInputStream mmps = new MimeBodyPartInputStream(stream, boundary, PUSHBACK_AMOUNT);
+ InputStream ins = AttachmentUtil.decode(mmps, val);
+ if (ins != mmps) {
+ ih.remove("Content-Transfer-Encoding");
+ }
+ body = new DelegatingInputStream(ins, this);
+ createCount++;
+ message.setContent(InputStream.class, body);
+ }
+ }
+
+ private String findBoundaryFromContentType(String ct) {
+ // Use regex to get the boundary and return null if it's not found
+ Matcher m = CONTENT_TYPE_BOUNDARY_PATTERN.matcher(ct);
+ return m.find() ? "--" + m.group(1) : null;
+ }
+
+ private String findBoundaryFromInputStream() throws IOException {
+ InputStream is = message.getContent(InputStream.class);
+ //boundary should definitely be in the first 2K;
+ PushbackInputStream in = new PushbackInputStream(is, 4096);
+ byte[] buf = new byte[2048];
+ int i = in.read(buf);
+ int len = i;
+ while (i > 0 && len < buf.length) {
+ i = in.read(buf, len, buf.length - len);
+ if (i > 0) {
+ len += i;
+ }
+ }
+ String msg = IOUtils.newStringFromBytes(buf, 0, len);
+ in.unread(buf, 0, len);
+
+ // Reset the input stream since we'll need it again later
+ message.setContent(InputStream.class, in);
+
+ // Use regex to get the boundary and return null if it's not found
+ Matcher m = INPUT_STREAM_BOUNDARY_PATTERN.matcher(msg);
+ return m.find() ? "--" + m.group(1) : null;
+ }
+
+ public AttachmentImpl readNext() throws IOException {
+ // Cache any mime parts that are currently being streamed
+ cacheStreamedAttachments();
+ if (closed) {
+ return null;
+ }
+
+ int v = stream.read();
+ if (v == -1) {
+ return null;
+ }
+ stream.unread(v);
+
+ Map<String, List<String>> headers = loadPartHeaders(stream);
+ return (AttachmentImpl)createAttachment(headers);
+ }
+
+ private void cacheStreamedAttachments() throws IOException {
+ if (body instanceof DelegatingInputStream
+ && !((DelegatingInputStream) body).isClosed()) {
+
+ cache((DelegatingInputStream) body);
+ }
+
+ List<Attachment> atts = new ArrayList<>(attachments.getLoadedAttachments());
+ for (Attachment a : atts) {
+ DataSource s = a.getDataHandler().getDataSource();
+ if (s instanceof AttachmentDataSource) {
+ AttachmentDataSource ads = (AttachmentDataSource)s;
+ if (!ads.isCached()) {
+ ads.cache(message);
+ }
+ } else if (s.getInputStream() instanceof DelegatingInputStream) {
+ cache((DelegatingInputStream) s.getInputStream());
+ } else {
+ //assume a normal stream that is already cached
+ }
+ }
+ }
+
+ private void cache(DelegatingInputStream input) throws IOException {
+ if (loaded.contains(input)) {
+ return;
+ }
+ loaded.add(input);
+ InputStream origIn = input.getInputStream();
+ try (CachedOutputStream out = new CachedOutputStream()) {
+ AttachmentUtil.setStreamedAttachmentProperties(message, out);
+ IOUtils.copy(input, out);
+ input.setInputStream(out.getInputStream());
+ origIn.close();
+ }
+ }
+
+ /**
+ * Move the read pointer to the begining of the first part read till the end
+ * of first boundary
+ *
+ * @param pushbackInStream
+ * @param boundary
+ * @throws IOException
+ */
+ private static boolean readTillFirstBoundary(PushbackInputStream pushbackInStream,
+ byte[] boundary) throws IOException {
+
+ // work around a bug in PushBackInputStream where the buffer isn't
+ // initialized
+ // and available always returns 0.
+ int value = pushbackInStream.read();
+ pushbackInStream.unread(value);
+ while (value != -1) {
+ value = pushbackInStream.read();
+ if ((byte) value == boundary[0]) {
+ int boundaryIndex = 0;
+ while (value != -1 && (boundaryIndex < boundary.length) && ((byte) value == boundary[boundaryIndex])) {
+
+ value = pushbackInStream.read();
+ if (value == -1) {
+ throw new IOException("Unexpected End while searching for first Mime Boundary");
+ }
+ boundaryIndex++;
+ }
+ if (boundaryIndex == boundary.length) {
+ // boundary found, read the newline
+ if (value == 13) {
+ pushbackInStream.read();
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Create an Attachment from the MIME stream. If there is a previous attachment
+ * that is not read, cache that attachment.
+ *
+ * @throws IOException
+ */
+ private Attachment createAttachment(Map<String, List<String>> headers) throws IOException {
+ InputStream partStream =
+ new DelegatingInputStream(new MimeBodyPartInputStream(stream, boundary, PUSHBACK_AMOUNT),
+ this);
+ createCount++;
+
+ return AttachmentUtil.createAttachment(partStream, headers);
+ }
+
+ public boolean isLazyLoading() {
+ return lazyLoading;
+ }
+
+ public void setLazyLoading(boolean lazyLoading) {
+ this.lazyLoading = lazyLoading;
+ }
+
+ public void markClosed(DelegatingInputStream delegatingInputStream) throws IOException {
+ closedCount++;
+ if (closedCount == createCount && !attachments.hasNext(false)) {
+ int x = stream.read();
+ while (x != -1) {
+ x = stream.read();
+ }
+ stream.close();
+ closed = true;
+ }
+ }
+ /**
+ * Check for more attachment.
+ *
+ * @return whether there is more attachment or not. It will not deserialize the next attachment.
+ * @throws IOException
+ */
+ public boolean hasNext() throws IOException {
+ cacheStreamedAttachments();
+ if (closed) {
+ return false;
+ }
+
+ int v = stream.read();
+ if (v == -1) {
+ return false;
+ }
+ stream.unread(v);
+ return true;
+ }
+
+
+
+ private Map<String, List<String>> loadPartHeaders(InputStream in) throws IOException {
+ StringBuilder buffer = new StringBuilder(128);
+ StringBuilder b = new StringBuilder(128);
+ Map<String, List<String>> heads = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
+ // loop until we hit the end or a null line
+ while (readLine(in, b)) {
+ // lines beginning with white space get special handling
+ char c = b.charAt(0);
+ if (c == ' ' || c == '\t') {
+ if (buffer.length() != 0) {
+ // preserve the line break and append the continuation
+ buffer.append("\r\n");
+ buffer.append(b);
+ }
+ } else {
+ // if we have a line pending in the buffer, flush it
+ if (buffer.length() > 0) {
+ addHeaderLine(heads, buffer);
+ buffer.setLength(0);
+ }
+ // add this to the accumulator
+ buffer.append(b);
+ }
+ }
+
+ // if we have a line pending in the buffer, flush it
+ if (buffer.length() > 0) {
+ addHeaderLine(heads, buffer);
+ }
+ return heads;
+ }
+
+ private boolean readLine(InputStream in, StringBuilder buffer) throws IOException {
+ if (buffer.length() != 0) {
+ buffer.setLength(0);
+ }
+ int c;
+
+ while ((c = in.read()) != -1) {
+ // a linefeed is a terminator, always.
+ if (c == '\n') {
+ break;
+ } else if (c == '\r') {
+ //just ignore the CR. The next character SHOULD be an NL. If not, we're
+ //just going to discard this
+ continue;
+ } else {
+ // just add to the buffer
+ buffer.append((char)c);
+ }
+
+ if (buffer.length() > maxHeaderLength) {
+ LOG.fine("The attachment header size has exceeded the configured parameter: " + maxHeaderLength);
+ throw new HeaderSizeExceededException();
+ }
+ }
+
+ // no characters found...this was either an eof or a null line.
+ return buffer.length() != 0;
+ }
+
+ private void addHeaderLine(Map<String, List<String>> heads, StringBuilder line) {
+ // null lines are a nop
+ final int size = line.length();
+ if (size == 0) {
+ return;
+ }
+ int separator = line.indexOf(":");
+ final String name;
+ String value = "";
+ if (separator == -1) {
+ name = line.toString().trim();
+ } else {
+ name = line.substring(0, separator);
+ // step past the separator. Now we need to remove any leading white space characters.
+ separator++;
+
+ while (separator < size) {
+ char ch = line.charAt(separator);
+ if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n') {
+ break;
+ }
+ separator++;
+ }
+ value = line.substring(separator);
+ }
+ List<String> v = heads.computeIfAbsent(name, k -> new ArrayList<>(1));
+ v.add(value);
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/attachment/AttachmentSerializer.java b/transform/src/patch/java/org/apache/cxf/attachment/AttachmentSerializer.java
new file mode 100644
index 0000000..3e65de3
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/attachment/AttachmentSerializer.java
@@ -0,0 +1,345 @@
+/**
+ * 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.attachment;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.activation.DataHandler;
+
+import org.apache.cxf.common.util.Base64Utility;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.message.Attachment;
+import org.apache.cxf.message.Message;
+
+
+
+
+public class AttachmentSerializer {
+ // http://tools.ietf.org/html/rfc2387
+ private static final String DEFAULT_MULTIPART_TYPE = "multipart/related";
+
+ private String contentTransferEncoding = AttachmentUtil.BINARY;
+
+ private Message message;
+ private String bodyBoundary;
+ private OutputStream out;
+ private String encoding;
+
+ private String multipartType;
+ private Map<String, List<String>> rootHeaders = Collections.emptyMap();
+ private boolean xop = true;
+ private boolean writeOptionalTypeParameters = true;
+
+
+ public AttachmentSerializer(Message messageParam) {
+ message = messageParam;
+ }
+
+ public AttachmentSerializer(Message messageParam,
+ String multipartType,
+ boolean writeOptionalTypeParameters,
+ Map<String, List<String>> headers) {
+ message = messageParam;
+ this.multipartType = multipartType;
+ this.writeOptionalTypeParameters = writeOptionalTypeParameters;
+ this.rootHeaders = headers;
+ }
+
+ /**
+ * Serialize the beginning of the attachment which includes the MIME
+ * beginning and headers for the root message.
+ */
+ public void writeProlog() throws IOException {
+ // Create boundary for body
+ bodyBoundary = AttachmentUtil.getUniqueBoundaryValue();
+
+ String bodyCt = (String) message.get(Message.CONTENT_TYPE);
+ String bodyCtParams = null;
+ String bodyCtParamsEscaped = null;
+ // split the bodyCt to its head that is the type and its properties so that we
+ // can insert the values at the right places based on the soap version and the mtom option
+ // bodyCt will be of the form
+ // soap11 -> text/xml
+ // soap12 -> application/soap+xml; action="urn:ihe:iti:2007:RetrieveDocumentSet"
+ if (bodyCt.indexOf(';') != -1) {
+ int pos = bodyCt.indexOf(';');
+ // get everything from the semi-colon
+ bodyCtParams = bodyCt.substring(pos);
+ bodyCtParamsEscaped = escapeQuotes(bodyCtParams);
+ // keep the type/subtype part in bodyCt
+ bodyCt = bodyCt.substring(0, pos);
+ }
+ // Set transport mime type
+ String requestMimeType = multipartType == null ? DEFAULT_MULTIPART_TYPE : multipartType;
+
+ StringBuilder ct = new StringBuilder(32);
+ ct.append(requestMimeType);
+
+ // having xop set to true implies multipart/related, but just in case...
+ boolean xopOrMultipartRelated = xop
+ || DEFAULT_MULTIPART_TYPE.equalsIgnoreCase(requestMimeType)
+ || DEFAULT_MULTIPART_TYPE.startsWith(requestMimeType);
+
+ // type is a required parameter for multipart/related only
+ if (xopOrMultipartRelated
+ && requestMimeType.indexOf("type=") == -1) {
+ if (xop) {
+ ct.append("; type=\"application/xop+xml\"");
+ } else {
+ ct.append("; type=\"").append(bodyCt).append('"');
+ }
+ }
+
+ // boundary
+ ct.append("; boundary=\"")
+ .append(bodyBoundary)
+ .append('"');
+
+ String rootContentId = getHeaderValue("Content-ID", AttachmentUtil.BODY_ATTACHMENT_ID);
+
+ // 'start' is a required parameter for XOP/MTOM, clearly defined
+ // for simpler multipart/related payloads but is not needed for
+ // multipart/mixed, multipart/form-data
+ if (xopOrMultipartRelated) {
+ ct.append("; start=\"<")
+ .append(checkAngleBrackets(rootContentId))
+ .append(">\"");
+ }
+
+ // start-info is a required parameter for XOP/MTOM, may be needed for
+ // other WS cases but is redundant in simpler multipart/related cases
+ // the parameters need to be included within the start-info's value in the escaped form
+ if (writeOptionalTypeParameters || xop) {
+ ct.append("; start-info=\"")
+ .append(bodyCt);
+ if (bodyCtParamsEscaped != null) {
+ ct.append(bodyCtParamsEscaped);
+ }
+ ct.append('"');
+ }
+
+
+ message.put(Message.CONTENT_TYPE, ct.toString());
+
+
+ // 2. write headers
+ out = message.getContent(OutputStream.class);
+ encoding = (String) message.get(Message.ENCODING);
+ if (encoding == null) {
+ encoding = StandardCharsets.UTF_8.name();
+ }
+ StringWriter writer = new StringWriter();
+ writer.write("\r\n");
+ writer.write("--");
+ writer.write(bodyBoundary);
+
+ StringBuilder mimeBodyCt = new StringBuilder();
+ String bodyType = getHeaderValue("Content-Type", null);
+ if (bodyType == null) {
+ mimeBodyCt.append(xop ? "application/xop+xml" : bodyCt)
+ .append("; charset=").append(encoding);
+ if (xop) {
+ mimeBodyCt.append("; type=\"").append(bodyCt);
+ if (bodyCtParamsEscaped != null) {
+ mimeBodyCt.append(bodyCtParamsEscaped);
+ }
+ mimeBodyCt.append('"');
+ } else if (bodyCtParams != null) {
+ mimeBodyCt.append(bodyCtParams);
+ }
+ } else {
+ mimeBodyCt.append(bodyType);
+ }
+
+ writeHeaders(mimeBodyCt.toString(), rootContentId, rootHeaders, writer);
+ out.write(writer.getBuffer().toString().getBytes(encoding));
+ }
+
+ private static String escapeQuotes(String s) {
+ return s.indexOf('"') != 0 ? s.replace("\"", "\\\"") : s;
+ }
+
+ public void setContentTransferEncoding(String cte) {
+ contentTransferEncoding = cte;
+ }
+
+ private String getHeaderValue(String name, String defaultValue) {
+ List<String> value = rootHeaders.get(name);
+ if (value == null || value.isEmpty()) {
+ return defaultValue;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < value.size(); i++) {
+ sb.append(value.get(i));
+ if (i + 1 < value.size()) {
+ sb.append(',');
+ }
+ }
+ return sb.toString();
+ }
+
+ private void writeHeaders(String contentType, String attachmentId,
+ Map<String, List<String>> headers, Writer writer) throws IOException {
+ writer.write("\r\nContent-Type: ");
+ writer.write(contentType);
+ writer.write("\r\nContent-Transfer-Encoding: " + contentTransferEncoding + "\r\n");
+
+ if (attachmentId != null) {
+ attachmentId = checkAngleBrackets(attachmentId);
+ writer.write("Content-ID: <");
+ writer.write(URLDecoder.decode(attachmentId, StandardCharsets.UTF_8.name()));
+ writer.write(">\r\n");
+ }
+ // headers like Content-Disposition need to be serialized
+ for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
+ String name = entry.getKey();
+ if ("Content-Type".equalsIgnoreCase(name) || "Content-ID".equalsIgnoreCase(name)
+ || "Content-Transfer-Encoding".equalsIgnoreCase(name)) {
+ continue;
+ }
+ writer.write(name);
+ writer.write(": ");
+ List<String> values = entry.getValue();
+ for (int i = 0; i < values.size(); i++) {
+ writer.write(values.get(i));
+ if (i + 1 < values.size()) {
+ writer.write(",");
+ }
+ }
+ writer.write("\r\n");
+ }
+
+ writer.write("\r\n");
+ }
+
+ private static String checkAngleBrackets(String value) {
+ if (value.charAt(0) == '<' && value.charAt(value.length() - 1) == '>') {
+ return value.substring(1, value.length() - 1);
+ }
+ return value;
+ }
+
+ /**
+ * Write the end of the body boundary and any attachments included.
+ * @throws IOException
+ */
+ public void writeAttachments() throws IOException {
+ if (message.getAttachments() != null) {
+ for (Attachment a : message.getAttachments()) {
+ StringWriter writer = new StringWriter();
+ writer.write("\r\n--");
+ writer.write(bodyBoundary);
+
+ final Map<String, List<String>> headers;
+ Iterator<String> it = a.getHeaderNames();
+ if (it.hasNext()) {
+ headers = new LinkedHashMap<>();
+ while (it.hasNext()) {
+ String key = it.next();
+ headers.put(key, Collections.singletonList(a.getHeader(key)));
+ }
+ } else {
+ headers = Collections.emptyMap();
+ }
+
+
+ DataHandler handler = a.getDataHandler();
+ handler.setCommandMap(AttachmentUtil.getCommandMap());
+
+ writeHeaders(handler.getContentType(), a.getId(),
+ headers, writer);
+ out.write(writer.getBuffer().toString().getBytes(encoding));
+ if ("base64".equals(contentTransferEncoding)) {
+ try (InputStream inputStream = handler.getInputStream()) {
+ encodeBase64(inputStream, out, IOUtils.DEFAULT_BUFFER_SIZE);
+ }
+ } else {
+ handler.writeTo(out);
+ }
+ }
+ }
+ StringWriter writer = new StringWriter();
+ writer.write("\r\n--");
+ writer.write(bodyBoundary);
+ writer.write("--");
+ out.write(writer.getBuffer().toString().getBytes(encoding));
+ out.flush();
+ }
+
+ private int encodeBase64(InputStream input, OutputStream output, int bufferSize) throws IOException {
+ int avail = input.available();
+ if (avail > 262143) {
+ //must be divisible by 3
+ avail = 262143;
+ }
+ if (avail > bufferSize) {
+ bufferSize = avail;
+ }
+ final byte[] buffer = new byte[bufferSize];
+ int n = input.read(buffer);
+ int total = 0;
+ while (-1 != n) {
+ if (n == 0) {
+ throw new IOException("0 bytes read in violation of InputStream.read(byte[])");
+ }
+ //make sure n is divisible by 3
+ int left = n % 3;
+ n -= left;
+ if (n > 0) {
+ Base64Utility.encodeAndStream(buffer, 0, n, output);
+ total += n;
+ }
+ if (left != 0) {
+ for (int x = 0; x < left; ++x) {
+ buffer[x] = buffer[n + x];
+ }
+ n = input.read(buffer, left, buffer.length - left);
+ if (n == -1) {
+ // we've hit the end, but still have stuff left, write it out
+ Base64Utility.encodeAndStream(buffer, 0, left, output);
+ total += left;
+ }
+ } else {
+ n = input.read(buffer);
+ }
+ }
+ return total;
+ }
+
+ public boolean isXop() {
+ return xop;
+ }
+
+ public void setXop(boolean xop) {
+ this.xop = xop;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/attachment/AttachmentUtil.java b/transform/src/patch/java/org/apache/cxf/attachment/AttachmentUtil.java
new file mode 100644
index 0000000..7c72f00
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/attachment/AttachmentUtil.java
@@ -0,0 +1,572 @@
+/**
+ * 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.attachment;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Logger;
+
+import javax.activation.CommandInfo;
+import javax.activation.CommandMap;
+import javax.activation.DataContentHandler;
+import javax.activation.DataHandler;
+import javax.activation.DataSource;
+import javax.activation.FileDataSource;
+import javax.activation.MailcapCommandMap;
+import javax.activation.URLDataSource;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.helpers.FileUtils;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.io.CachedOutputStream;
+import org.apache.cxf.message.Attachment;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageUtils;
+
+public final class AttachmentUtil {
+ public static final String BODY_ATTACHMENT_ID = "root.message@cxf.apache.org";
+
+ static final String BINARY = "binary";
+
+ private static final Logger LOG = LogUtils.getL7dLogger(AttachmentUtil.class);
+
+ private static final AtomicInteger COUNTER = new AtomicInteger();
+ private static final String ATT_UUID = UUID.randomUUID().toString();
+
+ private static final Random BOUND_RANDOM = new Random();
+ private static final CommandMap DEFAULT_COMMAND_MAP = CommandMap.getDefaultCommandMap();
+ private static final MailcapCommandMap COMMAND_MAP = new EnhancedMailcapCommandMap();
+
+
+ static final class EnhancedMailcapCommandMap extends MailcapCommandMap {
+ @Override
+ public synchronized DataContentHandler createDataContentHandler(
+ String mimeType) {
+ DataContentHandler dch = super.createDataContentHandler(mimeType);
+ if (dch == null) {
+ dch = DEFAULT_COMMAND_MAP.createDataContentHandler(mimeType);
+ }
+ return dch;
+ }
+
+ @Override
+ public DataContentHandler createDataContentHandler(String mimeType,
+ DataSource ds) {
+ DataContentHandler dch = super.createDataContentHandler(mimeType);
+ if (dch == null) {
+ dch = DEFAULT_COMMAND_MAP.createDataContentHandler(mimeType, ds);
+ }
+ return dch;
+ }
+
+ @Override
+ public synchronized CommandInfo[] getAllCommands(String mimeType) {
+ CommandInfo[] commands = super.getAllCommands(mimeType);
+ CommandInfo[] defaultCommands = DEFAULT_COMMAND_MAP.getAllCommands(mimeType);
+ List<CommandInfo> cmdList = new ArrayList<>(Arrays.asList(commands));
+
+ // Add CommandInfo which does not exist in current command map.
+ for (CommandInfo defCmdInfo : defaultCommands) {
+ String defCmdName = defCmdInfo.getCommandName();
+ boolean cmdNameExist = false;
+ for (CommandInfo cmdInfo : commands) {
+ if (cmdInfo.getCommandName().equals(defCmdName)) {
+ cmdNameExist = true;
+ break;
+ }
+ }
+ if (!cmdNameExist) {
+ cmdList.add(defCmdInfo);
+ }
+ }
+
+ CommandInfo[] allCommandArray = new CommandInfo[0];
+ return cmdList.toArray(allCommandArray);
+ }
+
+ @Override
+ public synchronized CommandInfo getCommand(String mimeType, String cmdName) {
+ CommandInfo cmdInfo = super.getCommand(mimeType, cmdName);
+ if (cmdInfo == null) {
+ cmdInfo = DEFAULT_COMMAND_MAP.getCommand(mimeType, cmdName);
+ }
+ return cmdInfo;
+ }
+
+ /**
+ * Merge current mime types and default mime types.
+ */
+ @Override
+ public synchronized String[] getMimeTypes() {
+ String[] mimeTypes = super.getMimeTypes();
+ String[] defMimeTypes = DEFAULT_COMMAND_MAP.getMimeTypes();
+ Set<String> mimeTypeSet = new HashSet<>();
+ Collections.addAll(mimeTypeSet, mimeTypes);
+ Collections.addAll(mimeTypeSet, defMimeTypes);
+ String[] mimeArray = new String[0];
+ return mimeTypeSet.toArray(mimeArray);
+ }
+ }
+
+
+ private AttachmentUtil() {
+
+ }
+
+ static {
+ COMMAND_MAP.addMailcap("image/*;;x-java-content-handler="
+ + ImageDataContentHandler.class.getName());
+ }
+
+ public static CommandMap getCommandMap() {
+ return COMMAND_MAP;
+ }
+
+ public static boolean isMtomEnabled(Message message) {
+ return MessageUtils.getContextualBoolean(message, Message.MTOM_ENABLED, false);
+ }
+
+ public static void setStreamedAttachmentProperties(Message message, CachedOutputStream bos)
+ throws IOException {
+ Object directory = message.getContextualProperty(AttachmentDeserializer.ATTACHMENT_DIRECTORY);
+ if (directory != null) {
+ if (directory instanceof File) {
+ bos.setOutputDir((File) directory);
+ } else if (directory instanceof String) {
+ bos.setOutputDir(new File((String) directory));
+ } else {
+ throw new IOException("The value set as " + AttachmentDeserializer.ATTACHMENT_DIRECTORY
+ + " should be either an instance of File or String");
+ }
+ }
+
+ Object threshold = message.getContextualProperty(AttachmentDeserializer.ATTACHMENT_MEMORY_THRESHOLD);
+ if (threshold != null) {
+ if (threshold instanceof Number) {
+ long t = ((Number) threshold).longValue();
+ if (t >= 0) {
+ bos.setThreshold(t);
+ } else {
+ LOG.warning("Threshold value overflowed long. Setting default value!");
+ bos.setThreshold(AttachmentDeserializer.THRESHOLD);
+ }
+ } else if (threshold instanceof String) {
+ try {
+ bos.setThreshold(Long.parseLong((String) threshold));
+ } catch (NumberFormatException e) {
+ throw new IOException("Provided threshold String is not a number", e);
+ }
+ } else {
+ throw new IOException("The value set as " + AttachmentDeserializer.ATTACHMENT_MEMORY_THRESHOLD
+ + " should be either an instance of Number or String");
+ }
+ } else if (!CachedOutputStream.isThresholdSysPropSet()) {
+ // Use the default AttachmentDeserializer Threshold only if there is no system property defined
+ bos.setThreshold(AttachmentDeserializer.THRESHOLD);
+ }
+
+ Object maxSize = message.getContextualProperty(AttachmentDeserializer.ATTACHMENT_MAX_SIZE);
+ if (maxSize != null) {
+ if (maxSize instanceof Number) {
+ long size = ((Number) maxSize).longValue();
+ if (size >= 0) {
+ bos.setMaxSize(size);
+ } else {
+ LOG.warning("Max size value overflowed long. Do not set max size!");
+ }
+ } else if (maxSize instanceof String) {
+ try {
+ bos.setMaxSize(Long.parseLong((String) maxSize));
+ } catch (NumberFormatException e) {
+ throw new IOException("Provided threshold String is not a number", e);
+ }
+ } else {
+ throw new IOException("The value set as " + AttachmentDeserializer.ATTACHMENT_MAX_SIZE
+ + " should be either an instance of Number or String");
+ }
+ }
+ }
+
+ public static String createContentID(String ns) throws UnsupportedEncodingException {
+ // tend to change
+ String cid = "cxf.apache.org";
+ if (ns != null && !ns.isEmpty()) {
+ try {
+ URI uri = new URI(ns);
+ String host = uri.getHost();
+ if (host != null) {
+ cid = host;
+ } else {
+ cid = ns;
+ }
+ } catch (Exception e) {
+ cid = ns;
+ }
+ }
+ return ATT_UUID + '-' + Integer.toString(COUNTER.incrementAndGet()) + '@'
+ + URLEncoder.encode(cid, StandardCharsets.UTF_8.name());
+ }
+
+ public static String getUniqueBoundaryValue() {
+ //generate a random UUID.
+ //we don't need the cryptographically secure random uuid that
+ //UUID.randomUUID() will produce. Thus, use a faster
+ //pseudo-random thing
+ long leastSigBits;
+ long mostSigBits;
+ synchronized (BOUND_RANDOM) {
+ mostSigBits = BOUND_RANDOM.nextLong();
+ leastSigBits = BOUND_RANDOM.nextLong();
+ }
+
+ mostSigBits &= 0xFFFFFFFFFFFF0FFFL; //clear version
+ mostSigBits |= 0x0000000000004000L; //set version
+
+ leastSigBits &= 0x3FFFFFFFFFFFFFFFL; //clear the variant
+ leastSigBits |= 0x8000000000000000L; //set to IETF variant
+
+ UUID result = new UUID(mostSigBits, leastSigBits);
+
+ return "uuid:" + result.toString();
+ }
+
+ public static Map<String, DataHandler> getDHMap(final Collection<Attachment> attachments) {
+ Map<String, DataHandler> dataHandlers = null;
+ if (attachments != null) {
+ if (attachments instanceof LazyAttachmentCollection) {
+ dataHandlers = ((LazyAttachmentCollection)attachments).createDataHandlerMap();
+ } else {
+ dataHandlers = new DHMap(attachments);
+ }
+ }
+ return dataHandlers == null ? new LinkedHashMap<>() : dataHandlers;
+ }
+
+ static class DHMap extends AbstractMap<String, DataHandler> {
+ final Collection<Attachment> list;
+ DHMap(Collection<Attachment> l) {
+ list = l;
+ }
+ public Set<Map.Entry<String, DataHandler>> entrySet() {
+ return new AbstractSet<Map.Entry<String, DataHandler>>() {
+ @Override
+ public Iterator<Map.Entry<String, DataHandler>> iterator() {
+ final Iterator<Attachment> it = list.iterator();
+ return new Iterator<Map.Entry<String, DataHandler>>() {
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+ public Map.Entry<String, DataHandler> next() {
+ final Attachment a = it.next();
+ return new Map.Entry<String, DataHandler>() {
+ @Override
+ public String getKey() {
+ return a.getId();
+ }
+
+ @Override
+ public DataHandler getValue() {
+ return a.getDataHandler();
+ }
+
+ @Override
+ public DataHandler setValue(DataHandler value) {
+ return null;
+ }
+ };
+ }
+ @Override
+ public void remove() {
+ it.remove();
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return list.size();
+ }
+ };
+ }
+
+ @Override
+ public DataHandler put(String key, DataHandler value) {
+ Iterator<Attachment> i = list.iterator();
+ DataHandler ret = null;
+ while (i.hasNext()) {
+ Attachment a = i.next();
+ if (a.getId().equals(key)) {
+ i.remove();
+ ret = a.getDataHandler();
+ break;
+ }
+ }
+ list.add(new AttachmentImpl(key, value));
+ return ret;
+ }
+ }
+
+ public static String cleanContentId(String id) {
+ if (id != null) {
+ if (id.startsWith("<")) {
+ // strip <>
+ id = id.substring(1, id.length() - 1);
+ }
+ // strip cid:
+ if (id.startsWith("cid:")) {
+ id = id.substring(4);
+ }
+ // urldecode. Is this bad even without cid:? What does decode do with malformed %-signs, anyhow?
+ try {
+ id = URLDecoder.decode(id, StandardCharsets.UTF_8.name());
+ } catch (UnsupportedEncodingException e) {
+ //ignore, keep id as is
+ }
+ }
+ if (id == null) {
+ //no Content-ID, set cxf default ID
+ id = BODY_ATTACHMENT_ID;
+ }
+ return id;
+ }
+
+ static String getHeaderValue(List<String> v) {
+ if (v != null && !v.isEmpty()) {
+ return v.get(0);
+ }
+ return null;
+ }
+ static String getHeaderValue(List<String> v, String delim) {
+ if (v != null && !v.isEmpty()) {
+ return String.join(delim, v);
+ }
+ return null;
+ }
+ static String getHeader(Map<String, List<String>> headers, String h) {
+ return getHeaderValue(headers.get(h));
+ }
+ static String getHeader(Map<String, List<String>> headers, String h, String delim) {
+ return getHeaderValue(headers.get(h), delim);
+ }
+ public static Attachment createAttachment(InputStream stream, Map<String, List<String>> headers)
+ throws IOException {
+
+ String id = cleanContentId(getHeader(headers, "Content-ID"));
+
+ AttachmentImpl att = new AttachmentImpl(id);
+
+ final String ct = getHeader(headers, "Content-Type");
+ String cd = getHeader(headers, "Content-Disposition");
+ String fileName = getContentDispositionFileName(cd);
+
+ String encoding = null;
+
+ for (Map.Entry<String, List<String>> e : headers.entrySet()) {
+ String name = e.getKey();
+ if ("Content-Transfer-Encoding".equalsIgnoreCase(name)) {
+ encoding = getHeader(headers, name);
+ if (BINARY.equalsIgnoreCase(encoding)) {
+ att.setXOP(true);
+ }
+ }
+ att.setHeader(name, getHeaderValue(e.getValue()));
+ }
+ if (encoding == null) {
+ encoding = BINARY;
+ }
+ InputStream ins = decode(stream, encoding);
+ if (ins != stream) {
+ headers.remove("Content-Transfer-Encoding");
+ }
+ DataSource source = new AttachmentDataSource(ct, ins);
+ if (!StringUtils.isEmpty(fileName)) {
+ ((AttachmentDataSource)source).setName(FileUtils.stripPath(fileName));
+ }
+ att.setDataHandler(new DataHandler(source));
+ return att;
+ }
+
+ static String getContentDispositionFileName(String cd) {
+ if (StringUtils.isEmpty(cd)) {
+ return null;
+ }
+ ContentDisposition c = new ContentDisposition(cd);
+ String s = c.getParameter("filename");
+ if (s == null) {
+ s = c.getParameter("name");
+ }
+ return s;
+ }
+
+ public static InputStream decode(InputStream in, String encoding) throws IOException {
+ if (encoding == null) {
+ return in;
+ }
+ encoding = encoding.toLowerCase();
+
+ // some encodings are just pass-throughs, with no real decoding.
+ if (BINARY.equals(encoding)
+ || "7bit".equals(encoding)
+ || "8bit".equals(encoding)) {
+ return in;
+ } else if ("base64".equals(encoding)) {
+ return new Base64DecoderStream(in);
+ } else if ("quoted-printable".equals(encoding)) {
+ return new QuotedPrintableDecoderStream(in);
+ } else {
+ throw new IOException("Unknown encoding " + encoding);
+ }
+ }
+ public static boolean isTypeSupported(String contentType, List<String> types) {
+ if (contentType == null) {
+ return false;
+ }
+ contentType = contentType.toLowerCase();
+ for (String s : types) {
+ if (contentType.indexOf(s) != -1) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static Attachment createMtomAttachment(boolean isXop, String mimeType, String elementNS,
+ byte[] data, int offset, int length, int threshold) {
+ if (!isXop || length <= threshold) {
+ return null;
+ }
+ if (mimeType == null) {
+ mimeType = "application/octet-stream";
+ }
+
+ ByteDataSource source = new ByteDataSource(data, offset, length);
+ source.setContentType(mimeType);
+ DataHandler handler = new DataHandler(source);
+
+ String id;
+ try {
+ id = AttachmentUtil.createContentID(elementNS);
+ } catch (UnsupportedEncodingException e) {
+ throw new Fault(e);
+ }
+ AttachmentImpl att = new AttachmentImpl(id, handler);
+ att.setXOP(isXop);
+ return att;
+ }
+
+ public static Attachment createMtomAttachmentFromDH(
+ boolean isXop, DataHandler handler, String elementNS, int threshold) {
+ if (!isXop) {
+ return null;
+ }
+
+ // The following is just wrong. Even if the DataHandler has a stream, we should still
+ // apply the threshold.
+ try {
+ DataSource ds = handler.getDataSource();
+ if (ds instanceof FileDataSource) {
+ FileDataSource fds = (FileDataSource)ds;
+ File file = fds.getFile();
+ if (file.length() < threshold) {
+ return null;
+ }
+ } else if (ds.getClass().getName().endsWith("ObjectDataSource")) {
+ Object o = handler.getContent();
+ if (o instanceof String
+ && ((String)o).length() < threshold) {
+ return null;
+ } else if (o instanceof byte[] && ((byte[])o).length < threshold) {
+ return null;
+ }
+ }
+ } catch (IOException e1) {
+ // ignore, just do the normal attachment thing
+ }
+
+ String id;
+ try {
+ id = AttachmentUtil.createContentID(elementNS);
+ } catch (UnsupportedEncodingException e) {
+ throw new Fault(e);
+ }
+ AttachmentImpl att = new AttachmentImpl(id, handler);
+ if (!StringUtils.isEmpty(handler.getName())) {
+ //set Content-Disposition attachment header if filename isn't null
+ String file = handler.getName();
+ File f = new File(file);
+ if (f.exists() && f.isFile()) {
+ file = f.getName();
+ }
+ att.setHeader("Content-Disposition", "attachment;name=\"" + file + "\"");
+ }
+ att.setXOP(isXop);
+ return att;
+ }
+
+ public static DataSource getAttachmentDataSource(String contentId, Collection<Attachment> atts) {
+ // Is this right? - DD
+ if (contentId.startsWith("cid:")) {
+ try {
+ contentId = URLDecoder.decode(contentId.substring(4), StandardCharsets.UTF_8.name());
+ } catch (UnsupportedEncodingException ue) {
+ contentId = contentId.substring(4);
+ }
+ return loadDataSource(contentId, atts);
+ } else if (contentId.indexOf("://") == -1) {
+ return loadDataSource(contentId, atts);
+ } else {
+ try {
+ return new URLDataSource(new URL(contentId));
+ } catch (MalformedURLException e) {
+ throw new Fault(e);
+ }
+ }
+
+ }
+
+ private static DataSource loadDataSource(String contentId, Collection<Attachment> atts) {
+ return new LazyDataSource(contentId, atts);
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/attachment/Base64DecoderStream.java b/transform/src/patch/java/org/apache/cxf/attachment/Base64DecoderStream.java
new file mode 100644
index 0000000..134553f
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/attachment/Base64DecoderStream.java
@@ -0,0 +1,196 @@
+/**
+ * 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.attachment;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.cxf.common.util.Base64Exception;
+import org.apache.cxf.common.util.Base64Utility;
+
+/**
+ * An implementation of a FilterInputStream that decodes the
+ * stream data in BASE64 encoding format. This version does the
+ * decoding "on the fly" rather than decoding a single block of
+ * data. Since this version is intended for use by the MimeUtilty class,
+ * it also handles line breaks in the encoded data.
+ */
+public class Base64DecoderStream extends FilterInputStream {
+
+ static final String MAIL_BASE64_IGNOREERRORS = "mail.mime.base64.ignoreerrors";
+
+ // number of decodeable units we'll try to process at one time. We'll attempt to read that much
+ // data from the input stream and decode in blocks.
+ static final int BUFFERED_UNITS = 2000;
+
+ // can be overridden by a system property.
+ protected boolean ignoreErrors;
+
+ // buffer for reading in chars for decoding (which can support larger bulk reads)
+ protected char[] encodedChars = new char[BUFFERED_UNITS * 4];
+ // a buffer for one decoding unit's worth of data (3 bytes).
+ protected byte[] decodedChars;
+ // count of characters in the buffer
+ protected int decodedCount;
+ // index of the next decoded character
+ protected int decodedIndex;
+
+
+ public Base64DecoderStream(InputStream in) {
+ super(in);
+ }
+
+ /**
+ * Test for the existance of decoded characters in our buffer
+ * of decoded data.
+ *
+ * @return True if we currently have buffered characters.
+ */
+ private boolean dataAvailable() {
+ return decodedCount != 0;
+ }
+
+ /**
+ * Decode a requested number of bytes of data into a buffer.
+ *
+ * @return true if we were able to obtain more data, false otherwise.
+ */
+ private boolean decodeStreamData() throws IOException {
+ decodedIndex = 0;
+
+ // fill up a data buffer with input data
+ int readCharacters = fillEncodedBuffer();
+
+ if (readCharacters > 0) {
+ try {
+ decodedChars = Base64Utility.decodeChunk(encodedChars, 0, readCharacters);
+ } catch (Base64Exception e) {
+ throw new IOException(e);
+ }
+ decodedCount = decodedChars.length;
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Retrieve a single byte from the decoded characters buffer.
+ *
+ * @return The decoded character or -1 if there was an EOF condition.
+ */
+ private int getByte() throws IOException {
+ if (!dataAvailable() && !decodeStreamData()) {
+ return -1;
+ }
+ decodedCount--;
+ // we need to ensure this doesn't get sign extended
+ return decodedChars[decodedIndex++] & 0xff;
+ }
+
+ private int getBytes(byte[] data, int offset, int length) throws IOException {
+
+ int readCharacters = 0;
+ while (length > 0) {
+ // need data? Try to get some
+ if (!dataAvailable() && !decodeStreamData()) {
+ // if we can't get this, return a count of how much we did get (which may be -1).
+ return readCharacters > 0 ? readCharacters : -1;
+ }
+
+ // now copy some of the data from the decoded buffer to the target buffer
+ int copyCount = Math.min(decodedCount, length);
+ System.arraycopy(decodedChars, decodedIndex, data, offset, copyCount);
+ decodedIndex += copyCount;
+ decodedCount -= copyCount;
+ offset += copyCount;
+ length -= copyCount;
+ readCharacters += copyCount;
+ }
+ return readCharacters;
+ }
+
+
+ /**
+ * Fill our buffer of input characters for decoding from the
+ * stream. This will attempt read a full buffer, but will
+ * terminate on an EOF or read error. This will filter out
+ * non-Base64 encoding chars and will only return a valid
+ * multiple of 4 number of bytes.
+ *
+ * @return The count of characters read.
+ */
+ private int fillEncodedBuffer() throws IOException {
+ int readCharacters = 0;
+
+ while (true) {
+ // get the next character from the stream
+ int ch = in.read();
+ // did we hit an EOF condition?
+ if (ch == -1) {
+ // now check to see if this is normal, or potentially an error
+ // if we didn't get characters as a multiple of 4, we may need to complain about this.
+ if ((readCharacters % 4) != 0) {
+ throw new IOException("Base64 encoding error, data truncated: " + readCharacters + " "
+ + new String(encodedChars, 0, readCharacters));
+ }
+ // return the count.
+ return readCharacters;
+ } else if (Base64Utility.isValidBase64(ch)) {
+ // if this character is valid in a Base64 stream, copy it to the buffer.
+ encodedChars[readCharacters++] = (char)ch;
+ // if we've filled up the buffer, time to quit.
+ if (readCharacters >= encodedChars.length) {
+ return readCharacters;
+ }
+ }
+
+ // we're filtering out whitespace and CRLF characters, so just ignore these
+ }
+ }
+
+
+ // in order to function as a filter, these streams need to override the different
+ // read() signature.
+
+ @Override
+ public int read() throws IOException {
+ return getByte();
+ }
+
+
+ @Override
+ public int read(byte [] buffer, int offset, int length) throws IOException {
+ return getBytes(buffer, offset, length);
+ }
+
+
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+
+ @Override
+ public int available() throws IOException {
+ return ((in.available() / 4) * 3) + decodedCount;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/attachment/ContentDisposition.java b/transform/src/patch/java/org/apache/cxf/attachment/ContentDisposition.java
new file mode 100644
index 0000000..f9d4524
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/attachment/ContentDisposition.java
@@ -0,0 +1,144 @@
+/**
+ * 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.attachment;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ContentDisposition {
+ private static final String CD_HEADER_PARAMS_EXPRESSION =
+ "[\\w-]++( )?\\*?=( )?((\"[^\"]++\")|([^;]+))";
+ private static final Pattern CD_HEADER_PARAMS_PATTERN =
+ Pattern.compile(CD_HEADER_PARAMS_EXPRESSION);
+
+ private static final String CD_HEADER_EXT_PARAMS_EXPRESSION =
+ "(?i)(UTF-8|ISO-8859-1)''((?:%[0-9a-f]{2}|\\S)+)";
+ private static final Pattern CD_HEADER_EXT_PARAMS_PATTERN =
+ Pattern.compile(CD_HEADER_EXT_PARAMS_EXPRESSION);
+ private static final Pattern CODEPOINT_ENCODED_VALUE_PATTERN = Pattern.compile("&#[0-9]{4};|\\S");
+
+ private static final String FILE_NAME = "filename";
+
+ private String value;
+ private String type;
+ private Map<String, String> params = new LinkedHashMap<>();
+
+ public ContentDisposition(String value) {
+ this.value = value;
+
+ String tempValue = value;
+
+ int index = tempValue.indexOf(';');
+ if (index > 0 && (tempValue.indexOf('=') >= index)) {
+ type = tempValue.substring(0, index).trim();
+ tempValue = tempValue.substring(index + 1);
+ }
+
+ String extendedFilename = null;
+ Matcher m = CD_HEADER_PARAMS_PATTERN.matcher(tempValue);
+ while (m.find()) {
+ final String paramName;
+ String paramValue = "";
+
+ String groupValue = m.group().trim();
+ int eqIndex = groupValue.indexOf('=');
+ if (eqIndex > 0) {
+ paramName = groupValue.substring(0, eqIndex).trim();
+ if (eqIndex + 1 != groupValue.length()) {
+ paramValue = groupValue.substring(eqIndex + 1).trim().replace("\"", "");
+ }
+ } else {
+ paramName = groupValue;
+ }
+ // filename* looks like the only CD param that is human readable
+ // and worthy of the extended encoding support. Other parameters
+ // can be supported if needed, see the complete list below
+ /*
+ http://www.iana.org/assignments/cont-disp/cont-disp.xhtml#cont-disp-2
+
+ filename name to be used when creating file [RFC2183]
+ creation-date date when content was created [RFC2183]
+ modification-date date when content was last modified [RFC2183]
+ read-date date when content was last read [RFC2183]
+ size approximate size of content in octets [RFC2183]
+ name original field name in form [RFC2388]
+ voice type or use of audio content [RFC2421]
+ handling whether or not processing is required [RFC3204]
+ */
+ if ("filename*".equalsIgnoreCase(paramName)) {
+ // try to decode the value if it matches the spec
+ try {
+ Matcher matcher = CD_HEADER_EXT_PARAMS_PATTERN.matcher(paramValue);
+ if (matcher.matches()) {
+ String encodingScheme = matcher.group(1);
+ String encodedValue = matcher.group(2);
+ paramValue = Rfc5987Util.decode(encodedValue, encodingScheme);
+ extendedFilename = paramValue;
+ }
+ } catch (UnsupportedEncodingException e) {
+ // would be odd not to support UTF-8 or 8859-1
+ }
+ } else if (FILE_NAME.equalsIgnoreCase(paramName) && paramValue.contains("&#")) {
+ Matcher matcher = CODEPOINT_ENCODED_VALUE_PATTERN.matcher(paramValue);
+ StringBuilder sb = new StringBuilder();
+ while (matcher.find()) {
+ String matched = matcher.group();
+ if (matched.startsWith("&#")) {
+ int codePoint = Integer.parseInt(matched.substring(2, 6));
+ sb.append(Character.toChars(codePoint));
+ } else {
+ sb.append(matched.charAt(0));
+ }
+ }
+ if (sb.length() > 0) {
+ paramValue = sb.toString();
+ }
+ }
+ params.put(paramName.toLowerCase(), paramValue);
+ }
+ if (extendedFilename != null) {
+ params.put(FILE_NAME, extendedFilename);
+ }
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getFilename() {
+ return params.get(FILE_NAME);
+ }
+
+ public String getParameter(String name) {
+ return params.get(name);
+ }
+
+ public Map<String, String> getParameters() {
+ return Collections.unmodifiableMap(params);
+ }
+
+ public String toString() {
+ return value;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/attachment/LazyAttachmentCollection.java b/transform/src/patch/java/org/apache/cxf/attachment/LazyAttachmentCollection.java
new file mode 100644
index 0000000..8dd4ace
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/attachment/LazyAttachmentCollection.java
@@ -0,0 +1,362 @@
+/**
+ * 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.attachment;
+
+import java.io.IOException;
+import java.util.AbstractCollection;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.activation.DataHandler;
+
+import org.apache.cxf.message.Attachment;
+
+public class LazyAttachmentCollection
+ implements Collection<Attachment> {
+
+ private AttachmentDeserializer deserializer;
+ private final List<Attachment> attachments = new ArrayList<>();
+ private final int maxAttachmentCount;
+
+ public LazyAttachmentCollection(AttachmentDeserializer deserializer, int maxAttachmentCount) {
+ super();
+ this.deserializer = deserializer;
+ this.maxAttachmentCount = maxAttachmentCount;
+ }
+
+ public List<Attachment> getLoadedAttachments() {
+ return attachments;
+ }
+
+ private void loadAll() {
+ try {
+ Attachment a = deserializer.readNext();
+ int count = 0;
+ while (a != null) {
+ attachments.add(a);
+ count++;
+ if (count > maxAttachmentCount) {
+ throw new IOException("The message contains more attachments than are permitted");
+ }
+ a = deserializer.readNext();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ /**
+ * Check for more attachments by attempting to deserialize the next attachment.
+ *
+ * @param shouldLoadNew if <i>false</i>, the "loaded attachments" List will not be changed.
+ * @return there is more attachment or not
+ * @throws IOException
+ */
+ public boolean hasNext(boolean shouldLoadNew) throws IOException {
+ if (shouldLoadNew) {
+ Attachment a = deserializer.readNext();
+ if (a != null) {
+ attachments.add(a);
+ return true;
+ }
+ return false;
+ }
+ return deserializer.hasNext();
+ }
+
+ public boolean hasNext() throws IOException {
+ return hasNext(true);
+ }
+ public Iterator<Attachment> iterator() {
+ return new Iterator<Attachment>() {
+ int current;
+ boolean removed;
+
+ public boolean hasNext() {
+ if (attachments.size() > current) {
+ return true;
+ }
+
+ // check if there is another attachment
+ try {
+ Attachment a = deserializer.readNext();
+ if (a == null) {
+ return false;
+ }
+ attachments.add(a);
+ return true;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Attachment next() {
+ Attachment a = attachments.get(current);
+ current++;
+ removed = false;
+ return a;
+ }
+
+ @Override
+ public void remove() {
+ if (removed) {
+ throw new IllegalStateException();
+ }
+ attachments.remove(--current);
+ removed = true;
+ }
+
+ };
+ }
+
+ public int size() {
+ loadAll();
+
+ return attachments.size();
+ }
+
+ public boolean add(Attachment arg0) {
+ return attachments.add(arg0);
+ }
+
+ public boolean addAll(Collection<? extends Attachment> arg0) {
+ return attachments.addAll(arg0);
+ }
+
+ public void clear() {
+ attachments.clear();
+ }
+
+ public boolean contains(Object arg0) {
+ return attachments.contains(arg0);
+ }
+
+ public boolean containsAll(Collection<?> arg0) {
+ return attachments.containsAll(arg0);
+ }
+
+ public boolean isEmpty() {
+ if (attachments.isEmpty()) {
+ return !iterator().hasNext();
+ }
+ return attachments.isEmpty();
+ }
+
+ public boolean remove(Object arg0) {
+ return attachments.remove(arg0);
+ }
+
+ public boolean removeAll(Collection<?> arg0) {
+ return attachments.removeAll(arg0);
+ }
+
+ public boolean retainAll(Collection<?> arg0) {
+ return attachments.retainAll(arg0);
+ }
+
+ public Object[] toArray() {
+ loadAll();
+
+ return attachments.toArray();
+ }
+
+ public <T> T[] toArray(T[] arg0) {
+ loadAll();
+
+ return attachments.toArray(arg0);
+ }
+
+ public Map<String, DataHandler> createDataHandlerMap() {
+ return new LazyAttachmentMap(this);
+ }
+
+ private static class LazyAttachmentMap implements Map<String, DataHandler> {
+ LazyAttachmentCollection collection;
+
+ LazyAttachmentMap(LazyAttachmentCollection c) {
+ collection = c;
+ }
+
+ public void clear() {
+ collection.clear();
+ }
+
+ public boolean containsKey(Object key) {
+ Iterator<Attachment> it = collection.iterator();
+ while (it.hasNext()) {
+ Attachment at = it.next();
+ if (key.equals(at.getId())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean containsValue(Object value) {
+ Iterator<Attachment> it = collection.iterator();
+ while (it.hasNext()) {
+ Attachment at = it.next();
+ if (value.equals(at.getDataHandler())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public DataHandler get(Object key) {
+ Iterator<Attachment> it = collection.iterator();
+ while (it.hasNext()) {
+ Attachment at = it.next();
+ if (key.equals(at.getId())) {
+ return at.getDataHandler();
+ }
+ }
+ return null;
+ }
+
+ public boolean isEmpty() {
+ return collection.isEmpty();
+ }
+ public int size() {
+ return collection.size();
+ }
+
+ public DataHandler remove(Object key) {
+ Iterator<Attachment> it = collection.iterator();
+ while (it.hasNext()) {
+ Attachment at = it.next();
+ if (key.equals(at.getId())) {
+ collection.remove(at);
+ return at.getDataHandler();
+ }
+ }
+ return null;
+ }
+ public DataHandler put(String key, DataHandler value) {
+ Attachment at = new AttachmentImpl(key, value);
+ collection.add(at);
+ return value;
+ }
+
+ public void putAll(Map<? extends String, ? extends DataHandler> t) {
+ for (Map.Entry<? extends String, ? extends DataHandler> ent : t.entrySet()) {
+ put(ent.getKey(), ent.getValue());
+ }
+ }
+
+
+ public Set<Map.Entry<String, DataHandler>> entrySet() {
+ return new AbstractSet<Map.Entry<String, DataHandler>>() {
+ public Iterator<Map.Entry<String, DataHandler>> iterator() {
+ return new Iterator<Map.Entry<String, DataHandler>>() {
+ Iterator<Attachment> it = collection.iterator();
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+ public Map.Entry<String, DataHandler> next() {
+ return new Map.Entry<String, DataHandler>() {
+ Attachment at = it.next();
+ public String getKey() {
+ return at.getId();
+ }
+ public DataHandler getValue() {
+ return at.getDataHandler();
+ }
+ public DataHandler setValue(DataHandler value) {
+ if (at instanceof AttachmentImpl) {
+ DataHandler h = at.getDataHandler();
+ ((AttachmentImpl)at).setDataHandler(value);
+ return h;
+ }
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ @Override
+ public void remove() {
+ it.remove();
+ }
+ };
+ }
+ public int size() {
+ return collection.size();
+ }
+ };
+ }
+
+ public Set<String> keySet() {
+ return new AbstractSet<String>() {
+ public Iterator<String> iterator() {
+ return new Iterator<String>() {
+ Iterator<Attachment> it = collection.iterator();
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ public String next() {
+ return it.next().getId();
+ }
+
+ @Override
+ public void remove() {
+ it.remove();
+ }
+ };
+ }
+
+ public int size() {
+ return collection.size();
+ }
+ };
+ }
+
+
+ public Collection<DataHandler> values() {
+ return new AbstractCollection<DataHandler>() {
+ public Iterator<DataHandler> iterator() {
+ return new Iterator<DataHandler>() {
+ Iterator<Attachment> it = collection.iterator();
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+ public DataHandler next() {
+ return it.next().getDataHandler();
+ }
+ @Override
+ public void remove() {
+ it.remove();
+ }
+ };
+ }
+
+ public int size() {
+ return collection.size();
+ }
+ };
+ }
+
+ }
+
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/attachment/MimeBodyPartInputStream.java b/transform/src/patch/java/org/apache/cxf/attachment/MimeBodyPartInputStream.java
new file mode 100644
index 0000000..ab80b89
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/attachment/MimeBodyPartInputStream.java
@@ -0,0 +1,275 @@
+/**
+ * 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.attachment;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+
+public class MimeBodyPartInputStream extends InputStream {
+
+ PushbackInputStream inStream;
+
+ boolean boundaryFound;
+ int pbAmount;
+ byte[] boundary;
+ byte[] boundaryBuffer;
+
+ private boolean closed;
+
+ public MimeBodyPartInputStream(PushbackInputStream inStreamParam,
+ byte[] boundaryParam,
+ int pbsize) {
+ super();
+ this.inStream = inStreamParam;
+ this.boundary = boundaryParam;
+ this.pbAmount = pbsize;
+ }
+
+ @Override
+ public int read(byte[] buf, int origOff, int origLen) throws IOException {
+ byte[] b = buf;
+ int off = origOff;
+ int len = origLen;
+ if (boundaryFound || closed) {
+ return -1;
+ }
+ if ((off < 0) || (off > b.length) || (len < 0)
+ || ((off + len) > b.length) || ((off + len) < 0)) {
+
+ throw new IndexOutOfBoundsException();
+ }
+ if (len == 0) {
+ return 0;
+ }
+ boolean bufferCreated = false;
+ if (len < boundary.length * 2) {
+ //buffer is too short to detect boundaries with it. We'll need to create a larger buffer
+ bufferCreated = true;
+ if (boundaryBuffer == null) {
+ boundaryBuffer = new byte[boundary.length * 2];
+ }
+ b = boundaryBuffer;
+ off = 0;
+ len = boundaryBuffer.length;
+ }
+ if (len > pbAmount) {
+ len = pbAmount; //can only pushback that much so make sure we can
+ }
+ int read = 0;
+ int idx = 0;
+ while (read >= 0 && idx < len && idx < (boundary.length * 2)) {
+ //make sure we read enough to detect the boundary
+ read = inStream.read(b, off + idx, len - idx);
+ if (read != -1) {
+ idx += read;
+ }
+ }
+ if (read == -1 && idx == 0) {
+ return -1;
+ }
+ len = idx;
+
+ int i = processBuffer(b, off, len);
+ if (bufferCreated && i > 0) {
+ // read more than we need, push it back
+ if (origLen >= i) {
+ System.arraycopy(b, 0, buf, origOff, i);
+ } else {
+ System.arraycopy(b, 0, buf, origOff, origLen);
+ inStream.unread(b, origLen, i - origLen);
+ i = origLen;
+ }
+ } else if (i == 0 && boundaryFound) {
+ return -1;
+ }
+
+ return i;
+ }
+
+ //Has Data after encountering CRLF
+ private boolean hasData(byte[] b, int initialPointer, int pointer, int off, int len)
+ throws IOException {
+ if (pointer < (off + len)) {
+ return true;
+ } else if (pointer >= 1000000000) {
+ inStream.unread(b, initialPointer, (off + len) - initialPointer);
+ return false;
+ } else {
+ int x = inStream.read();
+ if (x != -1) {
+ inStream.unread(x);
+ inStream.unread(b, initialPointer, (off + len) - initialPointer);
+ return false;
+ }
+ return true;
+ }
+ }
+
+ protected int processBuffer(byte[] buffer, int off, int len) throws IOException {
+ for (int i = off; i < (off + len); i++) {
+ boolean needUnread0d0a = false;
+ int value = buffer[i];
+ int initialI = i;
+ if (value == 13) {
+ if (!hasData(buffer, initialI, initialI + 1, off, len)) {
+ return initialI - off;
+ }
+ value = buffer[initialI + 1];
+ if (value != 10) {
+ continue;
+ }
+ if (!hasData(buffer, initialI, initialI + 2, off, len)) {
+ return initialI - off;
+ }
+ value = buffer[initialI + 2];
+ if ((byte) value != boundary[0]) {
+ i++;
+ continue;
+ }
+ needUnread0d0a = true;
+ i += 2; //i after this points to boundary[0] element
+ } else if (value != boundary[0]) {
+ continue;
+ }
+
+ int boundaryIndex = 0;
+ while ((boundaryIndex < boundary.length) && (value == boundary[boundaryIndex])) {
+ if (!hasData(buffer, initialI, i + 1, off, len)) {
+ return initialI - off;
+ }
+ value = buffer[++i];
+ boundaryIndex++;
+ }
+ if (boundaryIndex == boundary.length) {
+ // read the end of line character
+ if (initialI != off) {
+ i = 1000000000;
+ }
+ if (initialI - off != 0
+ && !hasData(buffer, initialI, i + 1, off, len)) {
+ return initialI - off;
+ }
+ boundaryFound = true;
+ int j = i + 1;
+ if (j < len && buffer[j] == 45 && value == 45) {
+ // Last mime boundary should have a succeeding "--"
+ // as we are on it, read the terminating CRLF
+ i += 2;
+ //last mime boundary
+ }
+
+ //boundary matched (may or may not be last mime boundary)
+ int processed = initialI - off;
+ if ((len - ((i - off) + 2)) > 0) {
+ inStream.unread(buffer, i + 2, len - (i + 2) + off);
+ }
+ return processed;
+ }
+
+ // Boundary not found. Restoring bytes skipped.
+ // write first skipped byte, push back the rest
+ if (value != -1) { //pushing back first byte of boundary
+ // Stream might have ended
+ i--;
+ }
+ if (needUnread0d0a) { //Pushing all, returning 13
+ i = i - boundaryIndex;
+ i--; //for 10
+// value = 13;
+ } else {
+ i = i - boundaryIndex;
+ i++;
+// value = boundary[0];
+ }
+ }
+ return len;
+ }
+
+ public int read() throws IOException {
+ boolean needUnread0d0a = false;
+ if (boundaryFound) {
+ return -1;
+ }
+
+ // read the next value from stream
+ int value = inStream.read();
+ // A problem occurred because all the mime parts tends to have a /r/n
+ // at the end. Making it hard to transform them to correct
+ // DataSources.
+ // This logic introduced to handle it
+ if (value == 13) {
+ value = inStream.read();
+ if (value != 10) {
+ inStream.unread(value);
+ return 13;
+ }
+ value = inStream.read();
+ if ((byte) value != boundary[0]) {
+ inStream.unread(value);
+ inStream.unread(10);
+ return 13;
+ }
+ needUnread0d0a = true;
+ } else if ((byte) value != boundary[0]) {
+ return value;
+ }
+ // read value is the first byte of the boundary. Start matching the
+ // next characters to find a boundary
+ int boundaryIndex = 0;
+ while ((boundaryIndex < boundary.length) && ((byte) value == boundary[boundaryIndex])) {
+ value = inStream.read();
+ boundaryIndex++;
+ }
+ if (boundaryIndex == boundary.length) {
+ // boundary found
+ boundaryFound = true;
+ int dashNext = inStream.read();
+ // read the end of line character
+ if (dashNext == 45 && value == 45) {
+ // Last mime boundary should have a succeeding "--"
+ // as we are on it, read the terminating CRLF
+ inStream.read();
+ inStream.read();
+ }
+ return -1;
+ }
+ // Boundary not found. Restoring bytes skipped.
+ // write first skipped byte, push back the rest
+ if (value != -1) {
+ // Stream might have ended
+ inStream.unread(value);
+ }
+ if (needUnread0d0a) {
+ inStream.unread(boundary, 0, boundaryIndex);
+ inStream.unread(10);
+ value = 13;
+ } else {
+ inStream.unread(boundary, 1, boundaryIndex - 1);
+ value = boundary[0];
+ }
+ return value;
+ }
+
+ @Override
+ public void close() throws IOException {
+ this.closed = true;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/bus/CXFBusFactory.java b/transform/src/patch/java/org/apache/cxf/bus/CXFBusFactory.java
new file mode 100644
index 0000000..fb3a833
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/bus/CXFBusFactory.java
@@ -0,0 +1,47 @@
+/**
+ * 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.bus;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.bus.extension.ExtensionManagerBus;
+
+public class CXFBusFactory extends BusFactory {
+
+ public Bus createBus() {
+ return createBus(new HashMap<>());
+ }
+
+ public Bus createBus(Map<Class<?>, Object> e) {
+ return createBus(e, new HashMap<>());
+ }
+
+ public Bus createBus(Map<Class<?>, Object> e, Map<String, Object> properties) {
+ ExtensionManagerBus bus = new ExtensionManagerBus(e, properties);
+ possiblySetDefaultBus(bus);
+ initializeBus(bus);
+ bus.initialize();
+ return bus;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/bus/blueprint/BundleDelegatingClassLoader.java b/transform/src/patch/java/org/apache/cxf/bus/blueprint/BundleDelegatingClassLoader.java
new file mode 100644
index 0000000..ff957c2
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/bus/blueprint/BundleDelegatingClassLoader.java
@@ -0,0 +1,134 @@
+/**
+ * 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.bus.blueprint;
+
+import java.io.IOException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+
+import org.osgi.framework.Bundle;
+
+/**
+ * A ClassLoader delegating to a given OSGi bundle.
+ */
+public class BundleDelegatingClassLoader extends ClassLoader {
+
+ private final Bundle bundle;
+ private final ClassLoader classLoader;
+
+ public BundleDelegatingClassLoader(Bundle bundle) {
+ this(bundle, null);
+ }
+
+ public BundleDelegatingClassLoader(Bundle bundle, ClassLoader classLoader) {
+ this.bundle = bundle;
+ this.classLoader = classLoader;
+ }
+
+ @Override
+ protected Class<?> findClass(final String name) throws ClassNotFoundException {
+ try {
+ return AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() {
+ public Class<?> run() throws ClassNotFoundException {
+ return bundle.loadClass(name);
+ }
+ });
+ } catch (PrivilegedActionException e) {
+ Exception cause = e.getException();
+
+ if (cause instanceof ClassNotFoundException) {
+ throw (ClassNotFoundException)cause;
+ }
+ throw (RuntimeException)cause;
+ }
+ }
+
+ @Override
+ protected URL findResource(final String name) {
+ URL resource = AccessController.doPrivileged(new PrivilegedAction<URL>() {
+ public URL run() {
+ return bundle.getResource(name);
+ }
+ });
+ if (classLoader != null && resource == null) {
+ resource = classLoader.getResource(name);
+ }
+ return resource;
+ }
+
+ @Override
+ protected Enumeration<URL> findResources(final String name) throws IOException {
+ Enumeration<URL> urls;
+ try {
+ urls = AccessController.doPrivileged(new PrivilegedExceptionAction<Enumeration<URL>>() {
+ public Enumeration<URL> run() throws IOException {
+ return bundle.getResources(name);
+ }
+
+ });
+ } catch (PrivilegedActionException e) {
+ Exception cause = e.getException();
+
+ if (cause instanceof IOException) {
+ throw (IOException)cause;
+ }
+ throw (RuntimeException)cause;
+ }
+
+ if (urls == null) {
+ urls = Collections.enumeration(new ArrayList<>());
+ }
+
+ return urls;
+ }
+
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ Class<?> clazz;
+ try {
+ clazz = findClass(name);
+ } catch (ClassNotFoundException cnfe) {
+ if (classLoader != null) {
+ try {
+ clazz = classLoader.loadClass(name);
+ } catch (ClassNotFoundException e) {
+ throw new ClassNotFoundException(name + " from bundle " + bundle.getBundleId()
+ + " (" + bundle.getSymbolicName() + ")", cnfe);
+ }
+ } else {
+ throw new ClassNotFoundException(name + " from bundle " + bundle.getBundleId()
+ + " (" + bundle.getSymbolicName() + ")", cnfe);
+ }
+ }
+ if (resolve) {
+ resolveClass(clazz);
+ }
+ return clazz;
+ }
+
+ public Bundle getBundle() {
+ return bundle;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/bus/blueprint/BusDefinitionParser.java b/transform/src/patch/java/org/apache/cxf/bus/blueprint/BusDefinitionParser.java
new file mode 100644
index 0000000..baf83b7
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/bus/blueprint/BusDefinitionParser.java
@@ -0,0 +1,72 @@
+/**
+ * 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.bus.blueprint;
+
+
+import org.w3c.dom.Element;
+
+import org.apache.aries.blueprint.ParserContext;
+import org.apache.aries.blueprint.mutable.MutableBeanMetadata;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.configuration.blueprint.AbstractBPBeanDefinitionParser;
+import org.osgi.service.blueprint.reflect.Metadata;
+
+public class BusDefinitionParser
+ extends AbstractBPBeanDefinitionParser {
+
+ public BusDefinitionParser() {
+ }
+
+ public Metadata parse(Element element, ParserContext context) {
+ String bname = element.hasAttribute("bus") ? element.getAttribute("bus") : "cxf";
+ String id = element.hasAttribute("id") ? element.getAttribute("id") : null;
+ MutableBeanMetadata cxfBean = getBus(context, bname);
+ parseAttributes(element, context, cxfBean);
+ parseChildElements(element, context, cxfBean);
+ context.getComponentDefinitionRegistry().removeComponentDefinition(bname);
+ if (!StringUtils.isEmpty(id)) {
+ cxfBean.addProperty("id", createValue(context, id));
+ }
+ return cxfBean;
+ }
+
+ @Override
+ protected void processBusAttribute(Element element, ParserContext ctx,
+ MutableBeanMetadata bean, String val) {
+ //nothing
+ }
+
+ @Override
+ protected boolean hasBusProperty() {
+ return false;
+ }
+
+
+ @Override
+ protected void mapElement(ParserContext ctx, MutableBeanMetadata bean, Element el, String name) {
+ if ("inInterceptors".equals(name) || "inFaultInterceptors".equals(name)
+ || "outInterceptors".equals(name) || "outFaultInterceptors".equals(name)
+ || "features".equals(name)) {
+ bean.addProperty(name, parseListData(ctx, bean, el));
+ } else if ("properties".equals(name)) {
+ bean.addProperty(name, parseMapData(ctx, bean, el));
+ }
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/bus/blueprint/ConfigurerImpl.java b/transform/src/patch/java/org/apache/cxf/bus/blueprint/ConfigurerImpl.java
new file mode 100644
index 0000000..37f82fe
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/bus/blueprint/ConfigurerImpl.java
@@ -0,0 +1,180 @@
+/**
+ * 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.bus.blueprint;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.configuration.Configurable;
+import org.apache.cxf.configuration.Configurer;
+import org.osgi.service.blueprint.container.BlueprintContainer;
+import org.osgi.service.blueprint.container.NoSuchComponentException;
+import org.osgi.service.blueprint.reflect.BeanMetadata;
+import org.osgi.service.blueprint.reflect.ComponentMetadata;
+
+/**
+ *
+ */
+public class ConfigurerImpl implements Configurer {
+ private static final Logger LOG = LogUtils.getL7dLogger(ConfigurerImpl.class);
+ BlueprintContainer container;
+
+ private final Map<String, List<MatcherHolder>> wildCardBeanDefinitions
+ = new TreeMap<>();
+
+ static class MatcherHolder implements Comparable<MatcherHolder> {
+ Matcher matcher;
+ String wildCardId;
+ MatcherHolder(String orig, Matcher matcher) {
+ wildCardId = orig;
+ this.matcher = matcher;
+ }
+ @Override
+ public int compareTo(MatcherHolder mh) {
+ int literalCharsLen1 = this.wildCardId.replace("*", "").length();
+ int literalCharsLen2 = mh.wildCardId.replace("*", "").length();
+ // The expression with more literal characters should end up on the top of the list
+ return Integer.compare(literalCharsLen1, literalCharsLen2) * -1;
+ }
+ }
+
+ public ConfigurerImpl(BlueprintContainer con) {
+ container = con;
+ initializeWildcardMap();
+ }
+ private boolean isWildcardBeanName(String bn) {
+ return bn.indexOf('*') != -1 || bn.indexOf('?') != -1
+ || (bn.indexOf('(') != -1 && bn.indexOf(')') != -1);
+ }
+
+ private void initializeWildcardMap() {
+ for (String s : container.getComponentIds()) {
+ if (isWildcardBeanName(s)) {
+ ComponentMetadata cmd = container.getComponentMetadata(s);
+ Class<?> cls = BlueprintBeanLocator.getClassForMetaData(container, cmd);
+ if (cls != null) {
+ final String cid = s.charAt(0) != '*' ? s
+ : "." + s.replaceAll("\\.", "\\."); //old wildcard
+ Matcher matcher = Pattern.compile(cid).matcher("");
+ List<MatcherHolder> m = wildCardBeanDefinitions.get(cls.getName());
+ if (m == null) {
+ m = new ArrayList<>();
+ wildCardBeanDefinitions.put(cls.getName(), m);
+ }
+ MatcherHolder holder = new MatcherHolder(s, matcher);
+ m.add(holder);
+ }
+ }
+ }
+ }
+
+ public void configureBean(Object beanInstance) {
+ configureBean(null, beanInstance, true);
+ }
+
+ public void configureBean(String bn, Object beanInstance) {
+ configureBean(bn, beanInstance, true);
+ }
+ public synchronized void configureBean(String bn, Object beanInstance, boolean checkWildcards) {
+ if (null == bn) {
+ bn = getBeanName(beanInstance);
+ }
+
+ if (null == bn) {
+ return;
+ }
+ if (checkWildcards) {
+ configureWithWildCard(bn, beanInstance);
+ }
+
+ if (container instanceof ExtendedBlueprintContainer) {
+ try {
+ final ComponentMetadata cm = container.getComponentMetadata(bn);
+ if (cm instanceof BeanMetadata) {
+ ((ExtendedBlueprintContainer)container).injectBeanInstance((BeanMetadata)cm, beanInstance);
+ }
+ } catch (NoSuchComponentException nsce) {
+ }
+ }
+ }
+
+ private void configureWithWildCard(String bn, Object beanInstance) {
+ if (!wildCardBeanDefinitions.isEmpty()) {
+ Class<?> clazz = beanInstance.getClass();
+ while (!Object.class.equals(clazz)) {
+ String className = clazz.getName();
+ List<MatcherHolder> matchers = wildCardBeanDefinitions.get(className);
+ if (matchers != null) {
+ for (MatcherHolder m : matchers) {
+ synchronized (m.matcher) {
+ m.matcher.reset(bn);
+ if (m.matcher.matches()) {
+ configureBean(m.wildCardId, beanInstance, false);
+ return;
+ }
+ }
+ }
+ }
+ clazz = clazz.getSuperclass();
+ }
+ }
+ }
+
+ protected String getBeanName(Object beanInstance) {
+ if (beanInstance instanceof Configurable) {
+ return ((Configurable)beanInstance).getBeanName();
+ }
+ String beanName = null;
+ Method m = null;
+ try {
+ m = beanInstance.getClass().getDeclaredMethod("getBeanName", (Class[])null);
+ } catch (NoSuchMethodException ex) {
+ try {
+ m = beanInstance.getClass().getMethod("getBeanName", (Class[])null);
+ } catch (NoSuchMethodException e) {
+ //ignore
+ }
+ }
+ if (m != null) {
+ try {
+ beanName = (String)(m.invoke(beanInstance));
+ } catch (Exception ex) {
+ LogUtils.log(LOG, Level.WARNING, "ERROR_DETERMINING_BEAN_NAME_EXC", ex);
+ }
+ }
+
+ if (null == beanName) {
+ LogUtils.log(LOG, Level.FINE, "COULD_NOT_DETERMINE_BEAN_NAME_MSG",
+ beanInstance.getClass().getName());
+ }
+
+ return beanName;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/bus/extension/Extension.java b/transform/src/patch/java/org/apache/cxf/bus/extension/Extension.java
new file mode 100644
index 0000000..03f0dc6
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/bus/extension/Extension.java
@@ -0,0 +1,291 @@
+/**
+ * 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.bus.extension;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.common.i18n.Message;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.StringUtils;
+
+public class Extension {
+ protected static final Logger LOG = LogUtils.getL7dLogger(Extension.class);
+
+ private static final String PROBLEM_CREATING_EXTENSION_CLASS = "PROBLEM_CREATING_EXTENSION_CLASS";
+
+ protected String className;
+ protected ClassLoader classloader;
+ protected volatile Class<?> clazz;
+ protected volatile Class<?> intf;
+ protected String interfaceName;
+ protected boolean deferred;
+ protected Collection<String> namespaces = new ArrayList<>();
+ protected Object[] args;
+ protected volatile Object obj;
+ protected boolean optional;
+ protected boolean notFound;
+
+
+ public Extension() {
+ }
+
+ public Extension(Class<?> cls, Class<?> inf) {
+ clazz = cls;
+ intf = inf;
+ interfaceName = inf.getName();
+ className = cls.getName();
+ classloader = cls.getClassLoader();
+ }
+ public Extension(Class<?> cls) {
+ clazz = cls;
+ className = cls.getName();
+ classloader = cls.getClassLoader();
+ }
+ public Extension(ClassLoader loader) {
+ classloader = loader;
+ }
+
+ public Extension(Extension ext) {
+ className = ext.className;
+ interfaceName = ext.interfaceName;
+ deferred = ext.deferred;
+ namespaces = ext.namespaces;
+ obj = ext.obj;
+ clazz = ext.clazz;
+ intf = ext.intf;
+ classloader = ext.classloader;
+ args = ext.args;
+ optional = ext.optional;
+ }
+
+ public void setOptional(boolean b) {
+ optional = b;
+ }
+ public boolean isOptional() {
+ return optional;
+ }
+
+ public String getName() {
+ return StringUtils.isEmpty(interfaceName) ? className : interfaceName;
+ }
+ public Object getLoadedObject() {
+ return obj;
+ }
+
+ public Extension cloneNoObject() {
+ Extension ext = new Extension(this);
+ ext.obj = null;
+ ext.clazz = null;
+ ext.intf = null;
+ return ext;
+ }
+
+ public String toString() {
+ StringBuilder buf = new StringBuilder(128);
+ buf.append("class: ");
+ buf.append(className);
+ buf.append(", interface: ");
+ buf.append(interfaceName);
+ buf.append(", deferred: ");
+ buf.append(deferred ? "true" : "false");
+ buf.append(", namespaces: (");
+ int n = 0;
+ for (String ns : namespaces) {
+ if (n > 0) {
+ buf.append(", ");
+ }
+ buf.append(ns);
+ n++;
+ }
+ buf.append(')');
+ return buf.toString();
+ }
+
+ public String getClassname() {
+ return className;
+ }
+
+ public void setClassname(String i) {
+ clazz = null;
+ notFound = false;
+ className = i;
+ }
+
+ public String getInterfaceName() {
+ return interfaceName;
+ }
+
+ public void setInterfaceName(String i) {
+ interfaceName = i;
+ notFound = false;
+ }
+
+ public boolean isDeferred() {
+ return deferred;
+ }
+
+ public void setDeferred(boolean d) {
+ deferred = d;
+ }
+
+ public Collection<String> getNamespaces() {
+ return namespaces;
+ }
+
+ public void setArgs(Object[] a) {
+ args = a;
+ }
+
+ protected Class<?> tryClass(String name, ClassLoader cl) {
+ Throwable origEx = null;
+ if (classloader != null) {
+ try {
+ return classloader.loadClass(name);
+ } catch (Throwable nex) {
+ //ignore, fall into the stuff below
+ //save the exception though as this is likely the important one
+ origEx = nex;
+ }
+ }
+ try {
+ return cl.loadClass(name);
+ } catch (Throwable ex) {
+ try {
+ // using the extension classloader as a fallback
+ return this.getClass().getClassLoader().loadClass(name);
+ } catch (Throwable nex) {
+ notFound = true;
+ if (!optional) {
+ throw new ExtensionException(new Message("PROBLEM_LOADING_EXTENSION_CLASS", LOG, name),
+ origEx != null ? origEx : ex);
+ }
+ }
+ }
+ return null;
+ }
+
+ public Class<?> getClassObject(ClassLoader cl) {
+ if (notFound) {
+ return null;
+ }
+ if (clazz != null) {
+ return clazz;
+ }
+ synchronized (this) {
+ if (clazz == null) {
+ clazz = tryClass(className, cl);
+ }
+ }
+ return clazz;
+ }
+ public Object load(ClassLoader cl, Bus b) {
+ if (obj != null) {
+ return obj;
+ }
+ Class<?> cls = getClassObject(cl);
+ try {
+ if (notFound) {
+ return null;
+ }
+ try {
+ //if there is a Bus constructor, use it.
+ if (b != null && args == null) {
+ Constructor<?> con = cls.getConstructor(Bus.class);
+ obj = con.newInstance(b);
+ return obj;
+ } else if (b != null && args != null) {
+ try {
+ obj = cls.getConstructor(Bus.class, Object[].class).newInstance(b, args);
+ } catch (NoSuchMethodException ex) { // no bus
+ obj = cls.getConstructor(Object[].class).newInstance(args);
+ }
+ return obj;
+ } else if (args != null) {
+ Constructor<?> con = cls.getConstructor(Object[].class);
+ obj = con.newInstance(args);
+ return obj;
+ }
+ } catch (InvocationTargetException ex) {
+ throw new ExtensionException(new Message(PROBLEM_CREATING_EXTENSION_CLASS, LOG, cls.getName()),
+ ex.getCause());
+ } catch (InstantiationException | SecurityException ex) {
+ throw new ExtensionException(new Message(PROBLEM_CREATING_EXTENSION_CLASS, LOG, cls.getName()), ex);
+ } catch (NoSuchMethodException e) {
+ //ignore
+ }
+ obj = cls.getConstructor().newInstance();
+ } catch (ExtensionException ex) {
+ notFound = true;
+ if (!optional) {
+ throw ex;
+ }
+ LOG.log(Level.FINE, "Could not load optional extension " + getName(), ex);
+ } catch (InvocationTargetException ex) {
+ notFound = true;
+ if (!optional) {
+ throw new ExtensionException(new Message(PROBLEM_CREATING_EXTENSION_CLASS, LOG, cls.getName()),
+ ex.getCause());
+ }
+ LOG.log(Level.FINE, "Could not load optional extension " + getName(), ex);
+ } catch (NoSuchMethodException ex) {
+ notFound = true;
+ List<Object> a = new ArrayList<>();
+ if (b != null) {
+ a.add(b);
+ }
+ if (args != null) {
+ a.add(args);
+ }
+ if (!optional) {
+ throw new ExtensionException(new Message("PROBLEM_FINDING_CONSTRUCTOR", LOG,
+ cls.getName(), a), ex);
+ }
+ LOG.log(Level.FINE, "Could not load optional extension " + getName(), ex);
+ } catch (Throwable e) {
+ notFound = true;
+ if (!optional) {
+ throw new ExtensionException(new Message(PROBLEM_CREATING_EXTENSION_CLASS, LOG, cls.getName()), e);
+ }
+ LOG.log(Level.FINE, "Could not load optional extension " + getName(), e);
+ }
+ return obj;
+ }
+
+ public Class<?> loadInterface(ClassLoader cl) {
+ if (intf != null || notFound) {
+ return intf;
+ }
+ synchronized (this) {
+ if (intf == null) {
+ intf = tryClass(interfaceName, cl);
+ }
+ }
+ return intf;
+ }
+
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/bus/extension/ExtensionManagerImpl.java b/transform/src/patch/java/org/apache/cxf/bus/extension/ExtensionManagerImpl.java
new file mode 100644
index 0000000..d4ce0be
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/bus/extension/ExtensionManagerImpl.java
@@ -0,0 +1,381 @@
+/**
+ * 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.bus.extension;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.common.injection.ResourceInjector;
+import org.apache.cxf.configuration.ConfiguredBeanLocator;
+import org.apache.cxf.configuration.Configurer;
+import org.apache.cxf.resource.ObjectTypeResolver;
+import org.apache.cxf.resource.ResourceManager;
+import org.apache.cxf.resource.ResourceResolver;
+import org.apache.cxf.resource.SinglePropertyResolver;
+
+public class ExtensionManagerImpl implements ExtensionManager, ConfiguredBeanLocator {
+ public static final String EXTENSIONMANAGER_PROPERTY_NAME = "extensionManager";
+ public static final String ACTIVATION_NAMESPACES_PROPERTY_NAME = "activationNamespaces";
+ public static final String ACTIVATION_NAMESPACES_SETTER_METHOD_NAME = "setActivationNamespaces";
+ public static final String BUS_EXTENSION_RESOURCE = "META-INF/cxf/bus-extensions.txt";
+
+ private final ClassLoader loader;
+ private ResourceManager resourceManager;
+ private Map<String, Extension> all = new ConcurrentHashMap<>();
+ private List<Extension> ordered = new CopyOnWriteArrayList<>();
+ private final Map<Class<?>, Object> activated;
+ private final Bus bus;
+
+ public ExtensionManagerImpl(ClassLoader cl, Map<Class<?>, Object> initialExtensions,
+ ResourceManager rm, Bus b) {
+ this(new String[] {BUS_EXTENSION_RESOURCE},
+ cl, initialExtensions, rm, b);
+ }
+ public ExtensionManagerImpl(String resource,
+ ClassLoader cl,
+ Map<Class<?>, Object> initialExtensions,
+ ResourceManager rm,
+ Bus b) {
+ this(new String[] {resource}, cl, initialExtensions, rm, b);
+ }
+ public ExtensionManagerImpl(String[] resources,
+ ClassLoader cl,
+ Map<Class<?>, Object> initialExtensions,
+ ResourceManager rm,
+ Bus b) {
+
+ loader = cl;
+ bus = b;
+ activated = initialExtensions;
+ resourceManager = rm;
+
+ ResourceResolver extensionManagerResolver =
+ new SinglePropertyResolver(EXTENSIONMANAGER_PROPERTY_NAME, this);
+ resourceManager.addResourceResolver(extensionManagerResolver);
+ resourceManager.addResourceResolver(new ObjectTypeResolver(this));
+
+ load(resources);
+ for (Map.Entry<String, Extension> ext : ExtensionRegistry.getRegisteredExtensions().entrySet()) {
+ if (!all.containsKey(ext.getKey())) {
+ all.put(ext.getKey(), ext.getValue());
+ ordered.add(ext.getValue());
+ }
+ }
+ }
+
+ final void load(String[] resources) {
+ if (resources == null) {
+ return;
+ }
+ try {
+ for (String resource : resources) {
+ load(resource);
+ }
+ } catch (IOException ex) {
+ throw new ExtensionException(ex);
+ }
+ }
+ public void add(Extension ex) {
+ all.put(ex.getName(), ex);
+ ordered.add(ex);
+ }
+
+ public void initialize() {
+ for (Extension e : ordered) {
+ if (!e.isDeferred() && e.getLoadedObject() == null) {
+ loadAndRegister(e);
+ }
+ }
+ }
+
+ public void removeBeansOfNames(List<String> names) {
+ for (String s : names) {
+ Extension ex = all.remove(s);
+ if (ex != null) {
+ ordered.remove(ex);
+ }
+ }
+ }
+ public void activateAll() {
+ for (Extension e : ordered) {
+ if (e.getLoadedObject() == null) {
+ loadAndRegister(e);
+ }
+ }
+ }
+ public <T> void activateAllByType(Class<T> type) {
+ for (Extension e : ordered) {
+ if (e.getLoadedObject() == null) {
+ Class<?> cls = e.getClassObject(loader);
+ if (cls != null && type.isAssignableFrom(cls)) {
+ synchronized (e) {
+ loadAndRegister(e);
+ }
+ }
+ }
+ }
+ }
+
+ public boolean hasBeanOfName(String name) {
+ return all.containsKey(name);
+ }
+
+ final void load(String resource) throws IOException {
+ if (loader != getClass().getClassLoader()) {
+ load(resource, getClass().getClassLoader());
+ }
+ load(resource, loader);
+ }
+ final synchronized void load(String resource, ClassLoader l) throws IOException {
+
+ Enumeration<URL> urls = l.getResources(resource);
+
+ while (urls.hasMoreElements()) {
+ final URL url = urls.nextElement();
+ try (InputStream is = AccessController.doPrivileged(new PrivilegedExceptionAction<InputStream>() {
+ public InputStream run() throws Exception {
+ return url.openStream();
+ }
+ })) {
+ List<Extension> exts = new TextExtensionFragmentParser(loader).getExtensions(is);
+ for (Extension e : exts) {
+ if (loader != l) {
+ e.classloader = l;
+ }
+ if (!all.containsKey(e.getName())) {
+ all.put(e.getName(), e);
+ ordered.add(e);
+ }
+ }
+ } catch (PrivilegedActionException pae) {
+ throw (IOException)pae.getException();
+ }
+ }
+ }
+
+ final void loadAndRegister(Extension e) {
+ Class<?> cls;
+ if (null != e.getInterfaceName() && !"".equals(e.getInterfaceName())) {
+ cls = e.loadInterface(loader);
+ } else {
+ cls = e.getClassObject(loader);
+ }
+ if (null != activated && null != cls && null != activated.get(cls)) {
+ return;
+ }
+
+ synchronized (e) {
+ Object obj = e.load(loader, bus);
+ if (obj == null) {
+ return;
+ }
+
+ if (null != activated) {
+ Configurer configurer = (Configurer)(activated.get(Configurer.class));
+ if (null != configurer) {
+ configurer.configureBean(obj);
+ }
+ }
+
+ // let the object know for which namespaces it has been activated
+ ResourceResolver namespacesResolver = null;
+ if (null != e.getNamespaces()) {
+ namespacesResolver = new SinglePropertyResolver(ACTIVATION_NAMESPACES_PROPERTY_NAME,
+ e.getNamespaces());
+ resourceManager.addResourceResolver(namespacesResolver);
+ }
+
+ // Since we need to support spring2.5 by removing @Resource("activationNamespaces")
+ // Now we call the setActivationNamespaces method directly here
+ if (e.getNamespaces() != null && !e.getNamespaces().isEmpty()) {
+ invokeSetterActivationNSMethod(obj, e.getNamespaces());
+ }
+
+ ResourceInjector injector = new ResourceInjector(resourceManager);
+
+ try {
+ injector.inject(obj);
+ injector.construct(obj);
+ } finally {
+ if (null != namespacesResolver) {
+ resourceManager.removeResourceResolver(namespacesResolver);
+ }
+ }
+
+ if (null != activated) {
+ if (cls == null) {
+ cls = obj.getClass();
+ }
+ activated.put(cls, obj);
+ }
+ }
+ }
+
+ public <T> T getExtension(String name, Class<T> type) {
+ if (name == null) {
+ return null;
+ }
+ Extension e = all.get(name);
+ if (e != null) {
+ Class<?> cls = e.getClassObject(loader);
+ if (cls != null && type.isAssignableFrom(cls)) {
+ synchronized (e) {
+ if (e.getLoadedObject() == null) {
+ loadAndRegister(e);
+ }
+ return type.cast(e.getLoadedObject());
+ }
+ }
+ }
+ return null;
+ }
+
+ private void invokeSetterActivationNSMethod(Object target, Object value) {
+ Class<?> clazz = target.getClass();
+ String methodName = ACTIVATION_NAMESPACES_SETTER_METHOD_NAME;
+ while (clazz != Object.class) {
+ Method[] methods = clazz.getMethods();
+ for (int i = 0; i < methods.length; i++) {
+ Method method = methods[i];
+ Class<?>[] params = method.getParameterTypes();
+ if (method.getName().equals(methodName) && params.length == 1) {
+ Class<?> paramType = params[0];
+ if (paramType.isInstance(value)) {
+ try {
+ method.invoke(target, new Object[] {value});
+ } catch (Exception e) {
+ // do nothing here
+ }
+ return;
+ }
+ }
+ }
+ clazz = clazz.getSuperclass();
+ }
+ }
+ public List<String> getBeanNamesOfType(Class<?> type) {
+ List<String> ret = new LinkedList<>();
+ for (Extension ex : ordered) {
+ Class<?> cls = ex.getClassObject(loader);
+ if (cls != null && type.isAssignableFrom(cls)) {
+ synchronized (ex) {
+ ret.add(ex.getName());
+ }
+ }
+ }
+ return ret;
+ }
+ public <T> T getBeanOfType(String name, Class<T> type) {
+ if (name == null) {
+ return null;
+ }
+ Extension ex = all.get(name);
+ if (ex != null) {
+ if (ex.getLoadedObject() == null) {
+ loadAndRegister(ex);
+ }
+ return type.cast(ex.getLoadedObject());
+ }
+ return null;
+ }
+ public <T> Collection<? extends T> getBeansOfType(Class<T> type) {
+ List<T> ret = new LinkedList<>();
+ Extension ext = all.get(type.getName());
+ if (ext != null) {
+ Class<?> cls = ext.getClassObject(loader);
+ if (cls != null && type.isAssignableFrom(cls)) {
+ synchronized (ext) {
+ if (ext.getLoadedObject() == null) {
+ loadAndRegister(ext);
+ }
+ if (ext.getLoadedObject() != null) {
+ ret.add(type.cast(ext.getLoadedObject()));
+ }
+ }
+ }
+ }
+ for (Extension ex : ordered) {
+ if (ex != ext) {
+ Class<?> cls = ex.getClassObject(loader);
+ if (cls != null && type.isAssignableFrom(cls)) {
+ synchronized (ex) {
+ if (ex.getLoadedObject() == null) {
+ loadAndRegister(ex);
+ }
+ if (ex.getLoadedObject() != null) {
+ ret.add(type.cast(ex.getLoadedObject()));
+ }
+ }
+ }
+ }
+ }
+ return ret;
+ }
+ public <T> boolean loadBeansOfType(Class<T> type, BeanLoaderListener<T> listener) {
+ boolean loaded = false;
+ for (Extension ex : ordered) {
+ Class<?> cls = ex.getClassObject(loader);
+ if (cls != null
+ && type.isAssignableFrom(cls)) {
+ synchronized (ex) {
+ if (listener.loadBean(ex.getName(), cls.asSubclass(type))) {
+ if (ex.getLoadedObject() == null) {
+ loadAndRegister(ex);
+ }
+ if (listener.beanLoaded(ex.getName(), type.cast(ex.getLoadedObject()))) {
+ return true;
+ }
+ loaded = true;
+ }
+ }
+ }
+ }
+ return loaded;
+ }
+ public boolean hasConfiguredPropertyValue(String beanName, String propertyName, String value) {
+ if (beanName == null) {
+ return false;
+ }
+ Extension ex = all.get(beanName);
+ return ex != null && ex.getNamespaces() != null
+ && ex.getNamespaces().contains(value);
+ }
+ public void destroyBeans() {
+ for (Extension ex : ordered) {
+ if (ex.getLoadedObject() != null) {
+ ResourceInjector injector = new ResourceInjector(resourceManager);
+ injector.destroy(ex.getLoadedObject());
+ }
+ }
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/bus/managers/ServiceContractResolverRegistryImpl.java b/transform/src/patch/java/org/apache/cxf/bus/managers/ServiceContractResolverRegistryImpl.java
new file mode 100644
index 0000000..a0f7af0
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/bus/managers/ServiceContractResolverRegistryImpl.java
@@ -0,0 +1,113 @@
+/**
+ * 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.bus.managers;
+
+import java.net.URI;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.xml.namespace.QName;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.common.injection.NoJSR250Annotations;
+import org.apache.cxf.endpoint.ServiceContractResolver;
+import org.apache.cxf.endpoint.ServiceContractResolverRegistry;
+
+/**
+ * A simple contract resolver registry. It maintains a list of contract resolvers in an
+ * <code>ArrayList</code>.
+ */
+@NoJSR250Annotations(unlessNull = "bus")
+public class ServiceContractResolverRegistryImpl implements ServiceContractResolverRegistry {
+
+ private List<ServiceContractResolver> resolvers
+ = new CopyOnWriteArrayList<>();
+
+ public ServiceContractResolverRegistryImpl() {
+
+ }
+ public ServiceContractResolverRegistryImpl(Bus b) {
+ setBus(b);
+ }
+
+
+ /**
+ * Sets the bus with which the registry is associated.
+ *
+ * @param bus
+ */
+ public final void setBus(Bus bus) {
+ if (bus != null) {
+ bus.setExtension(this, ServiceContractResolverRegistry.class);
+ }
+ }
+
+ /**
+ * Calls each of the registered <code>ServiceContractResolver</code> instances
+ * to resolve the location of the service's contract. It returns the location
+ * from the first resolver that matches the QName to a location.
+ *
+ * @param qname QName to be resolved into a contract location
+ * @return URI representing the location of the contract
+ */
+ public URI getContractLocation(QName qname) {
+ for (ServiceContractResolver resolver : resolvers) {
+ URI contractLocation = resolver.getContractLocation(qname);
+ if (null != contractLocation) {
+ return contractLocation;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Tests if a resolver is alreadey registered with this registry.
+ *
+ * @param resolver the contract resolver for which to searche
+ * @return <code>true</code> if the resolver is registered
+ */
+ public boolean isRegistered(ServiceContractResolver resolver) {
+ return resolvers.contains(resolver);
+ }
+
+ /**
+ * Registers a contract resolver with this registry.
+ *
+ * @param resolver the contract resolver to register
+ */
+ public synchronized void register(ServiceContractResolver resolver) {
+ resolvers.add(resolver);
+ }
+
+ /**
+ * Removes a contract resolver from this registry.
+ *
+ * @param resolver the contract resolver to remove
+ */
+ public synchronized void unregister(ServiceContractResolver resolver) {
+ resolvers.remove(resolver);
+ }
+
+
+ protected List<ServiceContractResolver> getResolvers() {
+ return resolvers;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/bus/osgi/CXFActivator.java b/transform/src/patch/java/org/apache/cxf/bus/osgi/CXFActivator.java
new file mode 100644
index 0000000..6504276
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/bus/osgi/CXFActivator.java
@@ -0,0 +1,134 @@
+/**
+ * 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.bus.osgi;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.cxf.bus.blueprint.BlueprintNameSpaceHandlerFactory;
+import org.apache.cxf.bus.blueprint.NamespaceHandlerRegisterer;
+import org.apache.cxf.bus.extension.Extension;
+import org.apache.cxf.bus.extension.ExtensionRegistry;
+import org.apache.cxf.common.util.CollectionUtils;
+import org.apache.cxf.internal.CXFAPINamespaceHandler;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * Is called in OSGi on start and stop of the cxf bundle.
+ * Manages
+ * - CXFBundleListener
+ * - Attaching ManagedWorkqueues to config admin service
+ * - OsgiBusListener
+ * - Blueprint namespaces
+ */
+public class CXFActivator implements BundleActivator {
+
+ private List<Extension> extensions;
+ private ManagedWorkQueueList workQueues;
+ private ServiceTracker<ConfigurationAdmin, ConfigurationAdmin> configAdminTracker;
+ private CXFExtensionBundleListener cxfBundleListener;
+ private ServiceRegistration<ManagedServiceFactory> workQueueServiceRegistration;
+
+
+
+ /** {@inheritDoc}*/
+ public void start(BundleContext context) throws Exception {
+ workQueues = new ManagedWorkQueueList();
+ cxfBundleListener = new CXFExtensionBundleListener(context.getBundle().getBundleId());
+ context.addBundleListener(cxfBundleListener);
+ cxfBundleListener.registerExistingBundles(context);
+
+ configAdminTracker = new ServiceTracker<>(context, ConfigurationAdmin.class, null);
+ configAdminTracker.open();
+ workQueues.setConfigAdminTracker(configAdminTracker);
+ workQueueServiceRegistration = registerManagedServiceFactory(context,
+ ManagedServiceFactory.class,
+ workQueues,
+ ManagedWorkQueueList.FACTORY_PID);
+
+ extensions = new ArrayList<>();
+ extensions.add(createOsgiBusListenerExtension(context));
+ extensions.add(createManagedWorkQueueListExtension(workQueues));
+ ExtensionRegistry.addExtensions(extensions);
+
+ BlueprintNameSpaceHandlerFactory factory = new BlueprintNameSpaceHandlerFactory() {
+
+ @Override
+ public Object createNamespaceHandler() {
+ return new CXFAPINamespaceHandler();
+ }
+ };
+ NamespaceHandlerRegisterer.register(context, factory,
+ "http://cxf.apache.org/blueprint/core",
+ "http://cxf.apache.org/configuration/beans",
+ "http://cxf.apache.org/configuration/parameterized-types",
+ "http://cxf.apache.org/configuration/security",
+ "http://schemas.xmlsoap.org/wsdl/",
+ "http://www.w3.org/2005/08/addressing",
+ "http://schemas.xmlsoap.org/ws/2004/08/addressing");
+
+ }
+
+ private <T> ServiceRegistration<T> registerManagedServiceFactory(BundleContext context,
+ Class<T> serviceClass,
+ T service,
+ String servicePid) {
+ return context.registerService(serviceClass, service,
+ CollectionUtils.singletonDictionary(Constants.SERVICE_PID, servicePid));
+ }
+
+ private Extension createOsgiBusListenerExtension(BundleContext context) {
+ Extension busListener = new Extension(OSGIBusListener.class);
+ busListener.setArgs(new Object[] {context});
+ return busListener;
+ }
+
+ private static Extension createManagedWorkQueueListExtension(final ManagedWorkQueueList workQueues) {
+ return new Extension(ManagedWorkQueueList.class) {
+
+ @Override
+ public Object getLoadedObject() {
+ return workQueues;
+ }
+
+ @Override
+ public Extension cloneNoObject() {
+ return this;
+ }
+ };
+ }
+
+ /** {@inheritDoc}*/
+ public void stop(BundleContext context) throws Exception {
+ context.removeBundleListener(cxfBundleListener);
+ cxfBundleListener.shutdown();
+ workQueues.shutDown();
+ workQueueServiceRegistration.unregister();
+ configAdminTracker.close();
+ ExtensionRegistry.removeExtensions(extensions);
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/bus/osgi/CXFExtensionBundleListener.java b/transform/src/patch/java/org/apache/cxf/bus/osgi/CXFExtensionBundleListener.java
new file mode 100644
index 0000000..ee9bb15
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/bus/osgi/CXFExtensionBundleListener.java
@@ -0,0 +1,181 @@
+/**
+ * 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.bus.osgi;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.logging.Logger;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.bus.extension.Extension;
+import org.apache.cxf.bus.extension.ExtensionException;
+import org.apache.cxf.bus.extension.ExtensionRegistry;
+import org.apache.cxf.bus.extension.TextExtensionFragmentParser;
+import org.apache.cxf.common.i18n.Message;
+import org.apache.cxf.common.logging.LogUtils;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.SynchronousBundleListener;
+
+public class CXFExtensionBundleListener implements SynchronousBundleListener {
+ private static final Logger LOG = LogUtils.getL7dLogger(CXFActivator.class);
+ private long id;
+ private ConcurrentMap<Long, List<OSGiExtension>> extensions
+ = new ConcurrentHashMap<>(16, 0.75f, 4);
+
+ public CXFExtensionBundleListener(long bundleId) {
+ this.id = bundleId;
+ }
+
+ public void registerExistingBundles(BundleContext context) {
+ for (Bundle bundle : context.getBundles()) {
+ if ((bundle.getState() == Bundle.RESOLVED
+ || bundle.getState() == Bundle.STARTING
+ || bundle.getState() == Bundle.ACTIVE
+ || bundle.getState() == Bundle.STOPPING)
+ && bundle.getBundleId() != context.getBundle().getBundleId()) {
+ register(bundle);
+ }
+ }
+ }
+
+ /** {@inheritDoc}*/
+ public void bundleChanged(BundleEvent event) {
+ if (event.getType() == BundleEvent.RESOLVED && id != event.getBundle().getBundleId()) {
+ register(event.getBundle());
+ } else if (event.getType() == BundleEvent.UNRESOLVED || event.getType() == BundleEvent.UNINSTALLED) {
+ unregister(event.getBundle().getBundleId());
+ }
+ }
+
+ protected void register(final Bundle bundle) {
+ Enumeration<?> e = bundle.findEntries("META-INF/cxf/", "bus-extensions.txt", false);
+ while (e != null && e.hasMoreElements()) {
+ List<Extension> orig = new TextExtensionFragmentParser(null).getExtensions((URL)e.nextElement());
+ addExtensions(bundle, orig);
+ }
+ }
+
+ private boolean addExtensions(final Bundle bundle, List<Extension> orig) {
+ if (orig.isEmpty()) {
+ return false;
+ }
+
+ List<String> names = new ArrayList<>(orig.size());
+ for (Extension ext : orig) {
+ names.add(ext.getName());
+ }
+ LOG.info("Adding the extensions from bundle " + bundle.getSymbolicName()
+ + " (" + bundle.getBundleId() + ") " + names);
+ List<OSGiExtension> list = extensions.get(bundle.getBundleId());
+ if (list == null) {
+ list = new CopyOnWriteArrayList<>();
+ List<OSGiExtension> preList = extensions.putIfAbsent(bundle.getBundleId(), list);
+ if (preList != null) {
+ list = preList;
+ }
+ }
+ for (Extension ext : orig) {
+ list.add(new OSGiExtension(ext, bundle));
+ }
+ ExtensionRegistry.addExtensions(list);
+ return !list.isEmpty();
+ }
+
+ protected void unregister(final long bundleId) {
+ List<OSGiExtension> list = extensions.remove(bundleId);
+ if (list != null) {
+ LOG.info("Removing the extensions for bundle " + bundleId);
+ ExtensionRegistry.removeExtensions(list);
+ }
+ }
+
+ public void shutdown() {
+ while (!extensions.isEmpty()) {
+ unregister(extensions.keySet().iterator().next());
+ }
+ }
+
+ public class OSGiExtension extends Extension {
+ final Bundle bundle;
+ Object serviceObject;
+ public OSGiExtension(Extension e, Bundle b) {
+ super(e);
+ bundle = b;
+ }
+
+ public void setServiceObject(Object o) {
+ serviceObject = o;
+ obj = o;
+ }
+
+ @Override
+ public Object load(ClassLoader cl, Bus b) {
+ if (interfaceName == null && bundle.getBundleContext() != null) {
+ ServiceReference<?> ref = bundle.getBundleContext().getServiceReference(className);
+ if (ref != null && ref.getBundle().getBundleId() == bundle.getBundleId()) {
+ Object o = bundle.getBundleContext().getService(ref);
+ serviceObject = o;
+ obj = o;
+ return obj;
+ }
+ }
+ return super.load(cl, b);
+ }
+
+ @Override
+ protected Class<?> tryClass(String name, ClassLoader cl) {
+ Class<?> c = null;
+ Throwable origExc = null;
+ try {
+ c = bundle.loadClass(className);
+ } catch (Throwable e) {
+ origExc = e;
+ }
+ if (c == null) {
+ try {
+ return super.tryClass(name, cl);
+ } catch (ExtensionException ee) {
+ if (origExc != null) {
+ throw new ExtensionException(new Message("PROBLEM_LOADING_EXTENSION_CLASS",
+ Extension.LOG, name),
+ origExc);
+ }
+ throw ee;
+ }
+ }
+ return c;
+ }
+
+ @Override
+ public Extension cloneNoObject() {
+ OSGiExtension ext = new OSGiExtension(this, bundle);
+ ext.obj = serviceObject;
+ return ext;
+ }
+
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/bus/osgi/OSGIBusListener.java b/transform/src/patch/java/org/apache/cxf/bus/osgi/OSGIBusListener.java
new file mode 100644
index 0000000..aad270d
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/bus/osgi/OSGIBusListener.java
@@ -0,0 +1,224 @@
+/**
+ * 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.bus.osgi;
+
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.bus.extension.ExtensionManagerImpl;
+import org.apache.cxf.buslifecycle.BusCreationListener;
+import org.apache.cxf.buslifecycle.BusLifeCycleListener;
+import org.apache.cxf.buslifecycle.BusLifeCycleManager;
+import org.apache.cxf.common.util.CollectionUtils;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.configuration.ConfiguredBeanLocator;
+import org.apache.cxf.endpoint.ClientLifeCycleListener;
+import org.apache.cxf.endpoint.ClientLifeCycleManager;
+import org.apache.cxf.endpoint.ServerLifeCycleListener;
+import org.apache.cxf.endpoint.ServerLifeCycleManager;
+import org.apache.cxf.feature.Feature;
+import org.apache.cxf.workqueue.WorkQueueManager;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.Version;
+
+public class OSGIBusListener implements BusLifeCycleListener {
+ public static final String CONTEXT_SYMBOLIC_NAME_PROPERTY = "cxf.context.symbolicname";
+ public static final String CONTEXT_VERSION_PROPERTY = "cxf.context.version";
+ public static final String CONTEXT_NAME_PROPERTY = "cxf.bus.id";
+
+ private static final String SERVICE_PROPERTY_PRIVATE = "org.apache.cxf.bus.private.extension";
+ private static final String SERVICE_PROPERTY_RESTRICTED = "org.apache.cxf.bus.restricted.extension";
+ private static final String BUS_EXTENSION_BUNDLES_EXCLUDES = "bus.extension.bundles.excludes";
+ Bus bus;
+ ServiceRegistration<?> service;
+ BundleContext defaultContext;
+ private Pattern extensionBundlesExcludesPattern;
+
+ public OSGIBusListener(Bus b) {
+ this(b, null);
+ }
+ public OSGIBusListener(Bus b, Object[] args) {
+ bus = b;
+ if (args != null && args.length > 0
+ && args[0] instanceof BundleContext) {
+ defaultContext = (BundleContext)args[0];
+ }
+ String extExcludes = (String)bus.getProperty(BUS_EXTENSION_BUNDLES_EXCLUDES);
+ if (!StringUtils.isEmpty(extExcludes)) {
+ try {
+ extensionBundlesExcludesPattern = Pattern.compile(extExcludes);
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ }
+ BusLifeCycleManager manager = bus.getExtension(BusLifeCycleManager.class);
+ manager.registerLifeCycleListener(this);
+ registerConfiguredBeanLocator();
+ registerClientLifeCycleListeners();
+ registerServerLifecycleListeners();
+ registerBusFeatures();
+ sendBusCreatedToBusCreationListeners();
+
+ }
+ private void registerConfiguredBeanLocator() {
+ final ConfiguredBeanLocator cbl = bus.getExtension(ConfiguredBeanLocator.class);
+ if (cbl instanceof ExtensionManagerImpl) {
+ // wire in the OSGi things
+ bus.setExtension(new OSGiBeanLocator(cbl, defaultContext),
+ ConfiguredBeanLocator.class);
+ }
+ }
+
+ public void initComplete() {
+ ManagedWorkQueueList wqList = bus.getExtension(ManagedWorkQueueList.class);
+ if (wqList != null) {
+ WorkQueueManager manager = bus.getExtension(WorkQueueManager.class);
+ wqList.addAllToWorkQueueManager(manager);
+ }
+ registerBusAsService();
+ }
+
+
+ public void preShutdown() {
+ }
+
+ public void postShutdown() {
+ if (service != null) {
+ service.unregister();
+ service = null;
+ }
+ }
+
+ private static ServiceReference<?>[] getServiceReferences(BundleContext context, Class<?> serviceClass) {
+ ServiceReference<?>[] refs = null;
+ try {
+ refs = context.getServiceReferences(serviceClass.getName(), null);
+ } catch (InvalidSyntaxException e) {
+ // ignore
+ }
+ if (refs == null) {
+ refs = new ServiceReference<?>[]{};
+ }
+ return refs;
+ }
+
+ private void sendBusCreatedToBusCreationListeners() {
+ ServiceReference<?>[] refs = getServiceReferences(defaultContext, BusCreationListener.class);
+ for (ServiceReference<?> ref : refs) {
+ if (!isPrivate(ref) && !isExcluded(ref)) {
+ BusCreationListener listener = (BusCreationListener)defaultContext.getService(ref);
+ listener.busCreated(bus);
+ }
+ }
+ }
+
+ private void registerServerLifecycleListeners() {
+ ServiceReference<?>[] refs = getServiceReferences(defaultContext, ServerLifeCycleListener.class);
+ ServerLifeCycleManager clcm = bus.getExtension(ServerLifeCycleManager.class);
+ for (ServiceReference<?> ref : refs) {
+ if (!isPrivate(ref) && !isExcluded(ref)) {
+ ServerLifeCycleListener listener = (ServerLifeCycleListener)defaultContext.getService(ref);
+ clcm.registerListener(listener);
+ }
+ }
+ }
+ private void registerClientLifeCycleListeners() {
+ ServiceReference<?>[] refs = getServiceReferences(defaultContext, ClientLifeCycleListener.class);
+ ClientLifeCycleManager clcm = bus.getExtension(ClientLifeCycleManager.class);
+ for (ServiceReference<?> ref : refs) {
+ if (!isPrivate(ref) && !isExcluded(ref)) {
+ ClientLifeCycleListener listener = (ClientLifeCycleListener)defaultContext.getService(ref);
+ clcm.registerListener(listener);
+ }
+ }
+ }
+
+ private void registerBusFeatures() {
+ ServiceReference<?>[] refs = getServiceReferences(defaultContext, Feature.class);
+ for (ServiceReference<?> ref : refs) {
+ if (!isPrivate(ref) && !isExcluded(ref)) {
+ Feature feature = (Feature)defaultContext.getService(ref);
+ bus.getFeatures().add(feature);
+ }
+ }
+ }
+
+ private boolean isPrivate(ServiceReference<?> ref) {
+ Object o = ref.getProperty(SERVICE_PROPERTY_PRIVATE);
+
+ if (o == null) {
+ return false;
+ }
+
+ Boolean pvt = Boolean.FALSE;
+ if (o instanceof String) {
+ pvt = Boolean.parseBoolean((String)o);
+ } else if (o instanceof Boolean) {
+ pvt = (Boolean)o;
+ }
+ return pvt.booleanValue();
+ }
+
+ private boolean isExcluded(ServiceReference<?> ref) {
+ String o = (String)ref.getProperty(SERVICE_PROPERTY_RESTRICTED);
+ if (!StringUtils.isEmpty(o)) {
+ // if the service's restricted-regex is set, the service is excluded when the app not matching that regex
+ BundleContext app = bus.getExtension(BundleContext.class);
+ try {
+ if (app != null && !app.getBundle().getSymbolicName().matches(o)) {
+ return true;
+ }
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ }
+ // if the excludes-regex is set, the service is excluded when matching that regex.
+ return extensionBundlesExcludesPattern != null
+ && extensionBundlesExcludesPattern.matcher(ref.getBundle().getSymbolicName()).matches();
+ }
+
+ private Version getBundleVersion(Bundle bundle) {
+ Dictionary<?, ?> headers = bundle.getHeaders();
+ String version = (String) headers.get(Constants.BUNDLE_VERSION);
+ return (version != null) ? Version.parseVersion(version) : Version.emptyVersion;
+ }
+
+
+
+ private void registerBusAsService() {
+ BundleContext context = bus.getExtension(BundleContext.class);
+ if (context != null) {
+ Map<String, Object> props = new HashMap<>();
+ props.put(CONTEXT_SYMBOLIC_NAME_PROPERTY, context.getBundle().getSymbolicName());
+ props.put(CONTEXT_VERSION_PROPERTY, getBundleVersion(context.getBundle()));
+ props.put(CONTEXT_NAME_PROPERTY, bus.getId());
+
+ service = context.registerService(Bus.class.getName(), bus, CollectionUtils.toDictionary(props));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/transform/src/patch/java/org/apache/cxf/bus/resource/ResourceManagerImpl.java b/transform/src/patch/java/org/apache/cxf/bus/resource/ResourceManagerImpl.java
new file mode 100644
index 0000000..99c6ee9
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/bus/resource/ResourceManagerImpl.java
@@ -0,0 +1,85 @@
+/**
+ * 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.bus.resource;
+
+import java.util.List;
+
+import javax.annotation.Resource;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.common.injection.NoJSR250Annotations;
+import org.apache.cxf.configuration.ConfiguredBeanLocator;
+import org.apache.cxf.extension.BusExtension;
+import org.apache.cxf.resource.DefaultResourceManager;
+import org.apache.cxf.resource.ObjectTypeResolver;
+import org.apache.cxf.resource.ResourceManager;
+import org.apache.cxf.resource.ResourceResolver;
+
+@NoJSR250Annotations(unlessNull = "bus")
+public class ResourceManagerImpl extends DefaultResourceManager implements BusExtension {
+
+ private Bus bus;
+
+ public ResourceManagerImpl() {
+ }
+
+ public ResourceManagerImpl(Bus b) {
+ super();
+ setBus(b);
+ }
+
+ @Override
+ protected void onFirstResolve() {
+ super.onFirstResolve();
+ if (bus != null) {
+ ConfiguredBeanLocator locator = bus.getExtension(ConfiguredBeanLocator.class);
+ if (locator != null) {
+ this.addResourceResolvers(locator.getBeansOfType(ResourceResolver.class));
+ }
+ }
+ }
+
+ /**
+ * Set the list of resolvers for this resource manager.
+ * @param resolvers
+ */
+ public final void setResolvers(List<? extends ResourceResolver> resolvers) {
+ registeredResolvers.clear();
+ registeredResolvers.addAll(resolvers);
+ }
+
+ @Resource
+ public final void setBus(Bus b) {
+ if (bus != b) {
+ bus = b;
+ firstCalled = false;
+ super.addResourceResolver(new ObjectTypeResolver(bus));
+ if (null != bus) {
+ bus.setExtension(this, ResourceManager.class);
+ }
+ }
+ }
+
+ public Class<?> getRegistrationType() {
+ return ResourceManager.class;
+ }
+
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/bus/spring/BusDefinitionParser.java b/transform/src/patch/java/org/apache/cxf/bus/spring/BusDefinitionParser.java
new file mode 100644
index 0000000..9d508b6
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/bus/spring/BusDefinitionParser.java
@@ -0,0 +1,310 @@
+/**
+ * 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.bus.spring;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.w3c.dom.Element;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.common.injection.NoJSR250Annotations;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.configuration.spring.AbstractBeanDefinitionParser;
+import org.apache.cxf.configuration.spring.BusWiringType;
+import org.apache.cxf.feature.Feature;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.interceptor.AbstractBasicInterceptorProvider;
+import org.apache.cxf.interceptor.Interceptor;
+import org.apache.cxf.message.Message;
+import org.springframework.beans.PropertyValue;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+public class BusDefinitionParser extends AbstractBeanDefinitionParser {
+ private static AtomicInteger counter = new AtomicInteger(0);
+
+ public BusDefinitionParser() {
+ super();
+ setBeanClass(BusConfig.class);
+ }
+
+ @Override
+ protected void doParse(Element element, ParserContext ctx, BeanDefinitionBuilder bean) {
+ String bus = element.getAttribute("bus");
+ if (StringUtils.isEmpty(bus)) {
+ bus = element.getAttribute("name");
+ if (StringUtils.isEmpty(bus)) {
+ element.setAttribute("bus", bus);
+ }
+ }
+ element.removeAttribute("name");
+ if (StringUtils.isEmpty(bus)) {
+ bus = "cxf";
+ }
+ String id = element.getAttribute("id");
+ if (!StringUtils.isEmpty(id)) {
+ bean.addPropertyValue("id", id);
+ }
+
+ super.doParse(element, ctx, bean);
+
+ if (ctx.getRegistry().containsBeanDefinition(bus)) {
+ BeanDefinition def = ctx.getRegistry().getBeanDefinition(bus);
+ copyProps(bean, def);
+ bean.addConstructorArgValue(bus);
+ } else if (!"cxf".equals(bus)) {
+ bean.getRawBeanDefinition().setBeanClass(SpringBus.class);
+ bean.setDestroyMethodName("shutdown");
+ try {
+ element.setUserData("ID", bus, null);
+ bean.getRawBeanDefinition().getPropertyValues().removePropertyValue("bus");
+ } catch (Throwable t) {
+ //likely not DOM level 3, ignore
+ }
+ } else {
+ addBusWiringAttribute(bean, BusWiringType.PROPERTY, bus, ctx);
+ bean.getRawBeanDefinition().setAttribute(WIRE_BUS_CREATE,
+ resolveId(element, null, ctx));
+ bean.addConstructorArgValue(bus);
+ }
+ }
+
+ @Override
+ protected boolean processBusAttribute(Element element, ParserContext ctx,
+ BeanDefinitionBuilder bean,
+ String val) {
+ return false;
+ }
+
+ private void copyProps(BeanDefinitionBuilder src, BeanDefinition def) {
+ for (PropertyValue v : src.getBeanDefinition().getPropertyValues().getPropertyValues()) {
+ if (!"bus".equals(v.getName())) {
+ def.getPropertyValues().addPropertyValue(v.getName(), v.getValue());
+ }
+ src.getBeanDefinition().getPropertyValues().removePropertyValue(v);
+ }
+
+ }
+
+ @Override
+ protected void mapElement(ParserContext ctx,
+ BeanDefinitionBuilder bean,
+ Element e,
+ String name) {
+ if ("inInterceptors".equals(name) || "inFaultInterceptors".equals(name)
+ || "outInterceptors".equals(name) || "outFaultInterceptors".equals(name)
+ || "features".equals(name)) {
+ List<?> list = ctx.getDelegate().parseListElement(e, bean.getBeanDefinition());
+ bean.addPropertyValue(name, list);
+ } else if ("properties".equals(name)) {
+ Map<?, ?> map = ctx.getDelegate().parseMapElement(e, bean.getBeanDefinition());
+ bean.addPropertyValue("properties", map);
+ }
+ }
+ @Override
+ protected String resolveId(Element element, AbstractBeanDefinition definition,
+ ParserContext ctx) {
+ String bus = null;
+ try {
+ bus = (String)element.getUserData("ID");
+ } catch (Throwable t) {
+ //ignore
+ }
+ if (bus == null) {
+ bus = element.getAttribute("bus");
+ if (StringUtils.isEmpty(bus)) {
+ bus = element.getAttribute("name");
+ }
+ if (StringUtils.isEmpty(bus)) {
+ bus = Bus.DEFAULT_BUS_ID + ".config" + counter.getAndIncrement();
+ } else {
+ bus = bus + ".config";
+ }
+ try {
+ element.setUserData("ID", bus, null);
+ } catch (Throwable t) {
+ //maybe no DOM level 3, ignore, but, may have issues with the counter
+ }
+ }
+ return bus;
+ }
+
+ @NoJSR250Annotations
+ public static class BusConfig extends AbstractBasicInterceptorProvider
+ implements ApplicationContextAware {
+
+ Bus bus;
+ String busName;
+ String id;
+ Collection<Feature> features;
+ Map<String, Object> properties;
+
+ public BusConfig(String busName) {
+ this.busName = busName;
+ }
+
+ public void setBus(Bus bb) {
+ if (bus == bb) {
+ return;
+ }
+ if (properties != null) {
+ bb.setProperties(properties);
+ properties = null;
+ }
+ if (!getInInterceptors().isEmpty()) {
+ bb.getInInterceptors().addAll(getInInterceptors());
+ }
+ if (!getOutInterceptors().isEmpty()) {
+ bb.getOutInterceptors().addAll(getOutInterceptors());
+ }
+ if (!getInFaultInterceptors().isEmpty()) {
+ bb.getInFaultInterceptors().addAll(getInFaultInterceptors());
+ }
+ if (!getOutFaultInterceptors().isEmpty()) {
+ bb.getOutFaultInterceptors().addAll(getOutFaultInterceptors());
+ }
+ if (!StringUtils.isEmpty(id)) {
+ bb.setId(id);
+ }
+ if (features != null) {
+ bb.setFeatures(features);
+ features = null;
+ }
+ bus = bb;
+ }
+
+ public void setApplicationContext(ApplicationContext applicationContext) {
+ if (bus != null) {
+ return;
+ }
+ }
+
+ @Override
+ public List<Interceptor<? extends Message>> getOutFaultInterceptors() {
+ if (bus != null) {
+ return bus.getOutFaultInterceptors();
+ }
+ return super.getOutFaultInterceptors();
+ }
+
+ @Override
+ public List<Interceptor<? extends Message>> getInFaultInterceptors() {
+ if (bus != null) {
+ return bus.getInFaultInterceptors();
+ }
+ return super.getInFaultInterceptors();
+ }
+
+ @Override
+ public List<Interceptor<? extends Message>> getInInterceptors() {
+ if (bus != null) {
+ return bus.getInInterceptors();
+ }
+ return super.getInInterceptors();
+ }
+
+ @Override
+ public List<Interceptor<? extends Message>> getOutInterceptors() {
+ if (bus != null) {
+ return bus.getOutInterceptors();
+ }
+ return super.getOutInterceptors();
+ }
+
+ @Override
+ public void setInInterceptors(List<Interceptor<? extends Message>> interceptors) {
+ if (bus != null) {
+ bus.getInInterceptors().addAll(interceptors);
+ } else {
+ super.setInInterceptors(interceptors);
+ }
+ }
+
+ @Override
+ public void setInFaultInterceptors(List<Interceptor<? extends Message>> interceptors) {
+ if (bus != null) {
+ bus.getInFaultInterceptors().addAll(interceptors);
+ } else {
+ super.setInFaultInterceptors(interceptors);
+ }
+ }
+
+ @Override
+ public void setOutInterceptors(List<Interceptor<? extends Message>> interceptors) {
+ if (bus != null) {
+ bus.getOutInterceptors().addAll(interceptors);
+ } else {
+ super.setOutInterceptors(interceptors);
+ }
+ }
+
+ @Override
+ public void setOutFaultInterceptors(List<Interceptor<? extends Message>> interceptors) {
+ if (bus != null) {
+ bus.getOutFaultInterceptors().addAll(interceptors);
+ } else {
+ super.setOutFaultInterceptors(interceptors);
+ }
+ }
+
+ public Collection<Feature> getFeatures() {
+ if (bus != null) {
+ return bus.getFeatures();
+ }
+ return features;
+ }
+
+ public void setFeatures(Collection<? extends Feature> features) {
+ if (bus != null) {
+ bus.setFeatures(features);
+ } else {
+ this.features = CastUtils.cast(features);
+ }
+
+ }
+
+ public Map<String, Object> getProperties() {
+ if (bus != null) {
+ return bus.getProperties();
+ }
+ return properties;
+ }
+ public void setProperties(Map<String, Object> s) {
+ if (bus != null) {
+ bus.setProperties(s);
+ } else {
+ this.properties = s;
+ }
+ }
+
+ public void setId(String s) {
+ id = s;
+ }
+
+
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/bus/spring/BusEntityResolver.java b/transform/src/patch/java/org/apache/cxf/bus/spring/BusEntityResolver.java
new file mode 100644
index 0000000..1e95e28
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/bus/spring/BusEntityResolver.java
@@ -0,0 +1,94 @@
+/**
+ * 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.bus.spring;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.springframework.beans.factory.xml.DelegatingEntityResolver;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PropertiesLoaderUtils;
+import org.springframework.util.CollectionUtils;
+
+/**
+ *
+ */
+public class BusEntityResolver extends DelegatingEntityResolver {
+
+ private static final Logger LOG = LogUtils.getL7dLogger(BusEntityResolver.class);
+
+ private EntityResolver dtdResolver;
+ private EntityResolver schemaResolver;
+ private Map<String, String> schemaMappings;
+ private ClassLoader classLoader;
+
+ public BusEntityResolver(ClassLoader loader, EntityResolver dr, EntityResolver sr) {
+ super(dr, sr);
+ classLoader = loader;
+ dtdResolver = dr;
+ schemaResolver = sr;
+
+ try {
+ Properties mappings = PropertiesLoaderUtils.loadAllProperties("META-INF/spring.schemas",
+ classLoader);
+ schemaMappings = new ConcurrentHashMap<>(mappings.size());
+ CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
+ } catch (IOException e) {
+ //ignore
+ }
+ }
+
+ @Override
+ public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
+ InputSource source = super.resolveEntity(publicId, systemId);
+ if (null == source && null != systemId) {
+ // try the schema and dtd resolver in turn, ignoring the suffix in publicId
+ LOG.log(Level.FINE, "Attempting to resolve systemId {0}", systemId);
+ source = schemaResolver.resolveEntity(publicId, systemId);
+ if (null == source) {
+ source = dtdResolver.resolveEntity(publicId, systemId);
+ }
+ }
+
+ if (null == source) {
+ return null;
+ }
+
+ String resourceLocation = schemaMappings.get(systemId);
+ if (resourceLocation != null && publicId == null) {
+ Resource resource = new ClassPathResource(resourceLocation, classLoader);
+ if (resource.exists()) {
+ source.setPublicId(systemId);
+ source.setSystemId(resource.getURL().toString());
+ }
+ }
+ return source;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/bus/spring/BusExtensionPostProcessor.java b/transform/src/patch/java/org/apache/cxf/bus/spring/BusExtensionPostProcessor.java
new file mode 100644
index 0000000..9389be6
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/bus/spring/BusExtensionPostProcessor.java
@@ -0,0 +1,71 @@
+/**
+ * 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.bus.spring;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.common.injection.NoJSR250Annotations;
+import org.apache.cxf.extension.BusExtension;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.Ordered;
+
+@NoJSR250Annotations
+public class BusExtensionPostProcessor implements BeanPostProcessor, ApplicationContextAware, Ordered {
+
+ private Bus bus;
+ private ApplicationContext context;
+
+ public void setApplicationContext(ApplicationContext ctx) {
+ context = ctx;
+ }
+
+ public int getOrder() {
+ return 1001;
+ }
+
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanId) {
+ return bean;
+ }
+
+ @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanId) {
+ if (bean instanceof BusExtension && null != getBus()) {
+ Class<? extends Object> cls = ((BusExtension)bean).getRegistrationType();
+ registerExt(bean, cls);
+ } else if (bean instanceof Bus && Bus.DEFAULT_BUS_ID.equals(beanId)) {
+ bus = (Bus)bean;
+ }
+ return bean;
+ }
+ private <T> void registerExt(Object bean, Class<T> cls) {
+ getBus().setExtension(cls.cast(bean), cls);
+ }
+
+ private Bus getBus() {
+ if (bus == null) {
+ bus = (Bus)context.getBean(Bus.DEFAULT_BUS_ID);
+ }
+ return bus;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/bus/spring/Jsr250BeanPostProcessor.java b/transform/src/patch/java/org/apache/cxf/bus/spring/Jsr250BeanPostProcessor.java
new file mode 100644
index 0000000..1fb0746
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/bus/spring/Jsr250BeanPostProcessor.java
@@ -0,0 +1,164 @@
+/**
+ * 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.bus.spring;
+
+
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.common.injection.ResourceInjector;
+import org.apache.cxf.resource.ResourceManager;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.Ordered;
+
+public class Jsr250BeanPostProcessor
+ implements DestructionAwareBeanPostProcessor, Ordered, ApplicationContextAware {
+
+ private ResourceManager resourceManager;
+ private ApplicationContext context;
+
+ private boolean isProcessing = true;
+ //private int count;
+ //private int count2;
+
+ Jsr250BeanPostProcessor() {
+ }
+
+ public void setApplicationContext(ApplicationContext applicationContext) {
+ context = applicationContext;
+ try {
+ Class<?> cls = Class
+ .forName("org.springframework.context.annotation.CommonAnnotationBeanPostProcessor");
+ isProcessing = context.getBeanNamesForType(cls, true, false).length == 0;
+ } catch (ClassNotFoundException e) {
+ isProcessing = true;
+ }
+ }
+
+ public int getOrder() {
+ return 1010;
+ }
+
+ private boolean injectable(Object bean, String beanId) {
+ return !"cxf".equals(beanId) && ResourceInjector.processable(bean.getClass(), bean);
+ }
+ private ResourceManager getResourceManager(Object bean) {
+ if (resourceManager == null) {
+ boolean temp = isProcessing;
+ isProcessing = false;
+ if (bean instanceof ResourceManager) {
+ resourceManager = (ResourceManager)bean;
+ resourceManager.addResourceResolver(new BusApplicationContextResourceResolver(context));
+ } else if (bean instanceof Bus) {
+ Bus b = (Bus)bean;
+ ResourceManager m = b.getExtension(ResourceManager.class);
+ if (m != null) {
+ resourceManager = m;
+ if (!(b instanceof SpringBus)) {
+ resourceManager
+ .addResourceResolver(new BusApplicationContextResourceResolver(context));
+ }
+ }
+ } else {
+ ResourceManager m = null;
+ Bus b = null;
+ try {
+ m = (ResourceManager)context.getBean(ResourceManager.class.getName());
+ } catch (NoSuchBeanDefinitionException t) {
+ //ignore - no resource manager
+ }
+ if (m == null) {
+ b = (Bus)context.getBean("cxf");
+ m = b.getExtension(ResourceManager.class);
+ }
+ if (m != null) {
+ resourceManager = m;
+ if (!(b instanceof SpringBus)) {
+ resourceManager
+ .addResourceResolver(new BusApplicationContextResourceResolver(context));
+ }
+ }
+ }
+ isProcessing = temp;
+ }
+ return resourceManager;
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanId) {
+ if (!isProcessing) {
+ if (resourceManager == null && bean instanceof ResourceManager) {
+ resourceManager = (ResourceManager)bean;
+ resourceManager.addResourceResolver(new BusApplicationContextResourceResolver(context));
+ }
+ return bean;
+ }
+ if (bean != null
+ && injectable(bean, beanId)) {
+ new ResourceInjector(getResourceManager(bean)).construct(bean);
+ }
+ return bean;
+ }
+
+ @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanId) {
+ if (!isProcessing) {
+ return bean;
+ }
+ if (bean instanceof Bus) {
+ getResourceManager(bean);
+ }
+ /*
+ if (bean.getClass().getName().contains("Corb")) {
+ Thread.dumpStack();
+ }
+ */
+
+ if (bean != null
+ && injectable(bean, beanId)) {
+ new ResourceInjector(getResourceManager(bean)).inject(bean);
+ /*
+ System.out.println("p :" + (++count) + ": " + bean.getClass().getName() + " " + beanId);
+ } else if (bean != null) {
+ System.out.println("np: " + (++count2)
+ + ": " + bean.getClass().getName() + " " + beanId);
+ */
+ }
+ return bean;
+ }
+
+ public void postProcessBeforeDestruction(Object bean, String beanId) {
+ if (!isProcessing) {
+ return;
+ }
+ if (bean != null
+ && injectable(bean, beanId)) {
+ new ResourceInjector(getResourceManager(bean)).destroy(bean);
+ }
+ }
+
+ @Override
+ public boolean requiresDestruction(Object bean) {
+ return isProcessing;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/bus/spring/NamespaceHandler.java b/transform/src/patch/java/org/apache/cxf/bus/spring/NamespaceHandler.java
new file mode 100644
index 0000000..9211ea8
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/bus/spring/NamespaceHandler.java
@@ -0,0 +1,57 @@
+/**
+ * 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.bus.spring;
+
+import org.w3c.dom.Element;
+
+import org.apache.cxf.configuration.spring.SimpleBeanDefinitionParser;
+import org.apache.cxf.feature.FastInfosetFeature;
+import org.apache.cxf.workqueue.AutomaticWorkQueueImpl;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
+import org.springframework.beans.factory.xml.ParserContext;
+
+public class NamespaceHandler extends NamespaceHandlerSupport {
+ @SuppressWarnings("deprecation")
+ public void init() {
+ registerBeanDefinitionParser("bus",
+ new BusDefinitionParser());
+ registerBeanDefinitionParser("logging",
+ new SimpleBeanDefinitionParser(org.apache.cxf.feature.LoggingFeature.class));
+ registerBeanDefinitionParser("fastinfoset",
+ new SimpleBeanDefinitionParser(FastInfosetFeature.class));
+
+ registerBeanDefinitionParser("workqueue",
+ new SimpleBeanDefinitionParser(AutomaticWorkQueueImpl.class) {
+
+ @Override
+ protected void processNameAttribute(Element element,
+ ParserContext ctx,
+ BeanDefinitionBuilder bean,
+ String val) {
+ bean.addPropertyValue("name", val);
+ element.removeAttribute("name");
+ if (!element.hasAttribute("id")) {
+ element.setAttribute("id", "cxf.workqueue." + val);
+ }
+
+ }
+ });
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/bus/spring/SpringBus.java b/transform/src/patch/java/org/apache/cxf/bus/spring/SpringBus.java
new file mode 100644
index 0000000..6616c8e
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/bus/spring/SpringBus.java
@@ -0,0 +1,142 @@
+/**
+ * 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.bus.spring;
+
+import org.apache.cxf.bus.extension.ExtensionManagerBus;
+import org.apache.cxf.configuration.ConfiguredBeanLocator;
+import org.apache.cxf.configuration.Configurer;
+import org.apache.cxf.configuration.spring.ConfigurerImpl;
+import org.apache.cxf.resource.ResourceManager;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.context.support.AbstractApplicationContext;
+
+/**
+ *
+ */
+public class SpringBus extends ExtensionManagerBus
+ implements ApplicationContextAware {
+
+ AbstractApplicationContext ctx;
+ boolean closeContext;
+
+ public SpringBus() {
+ }
+
+ public void setBusConfig(BusDefinitionParser.BusConfig bc) {
+ bc.setBus(this);
+ }
+
+ /** {@inheritDoc}*/
+ public void setApplicationContext(ApplicationContext applicationContext) {
+ ctx = (AbstractApplicationContext)applicationContext;
+ @SuppressWarnings("rawtypes")
+ ApplicationListener listener = new ApplicationListener() {
+ public void onApplicationEvent(ApplicationEvent event) {
+ SpringBus.this.onApplicationEvent(event);
+ }
+ };
+ ctx.addApplicationListener(listener);
+ ApplicationContext ac = applicationContext.getParent();
+ while (ac != null) {
+ if (ac instanceof AbstractApplicationContext) {
+ ((AbstractApplicationContext)ac).addApplicationListener(listener);
+ }
+ ac = ac.getParent();
+ }
+
+ // set the classLoader extension with the application context classLoader
+ setExtension(applicationContext.getClassLoader(), ClassLoader.class);
+
+ setExtension(new ConfigurerImpl(applicationContext), Configurer.class);
+
+ ResourceManager m = getExtension(ResourceManager.class);
+ m.addResourceResolver(new BusApplicationContextResourceResolver(applicationContext));
+
+ setExtension(applicationContext, ApplicationContext.class);
+ ConfiguredBeanLocator loc = getExtension(ConfiguredBeanLocator.class);
+ if (!(loc instanceof SpringBeanLocator)) {
+ setExtension(new SpringBeanLocator(applicationContext, this), ConfiguredBeanLocator.class);
+ }
+ if (getState() != BusState.RUNNING) {
+ initialize();
+ }
+ }
+
+ public void onApplicationEvent(ApplicationEvent event) {
+ if (ctx == null) {
+ return;
+ }
+ boolean doIt = false;
+ ApplicationContext ac = ctx;
+ while (ac != null) {
+ if (event.getSource() == ac) {
+ doIt = true;
+ break;
+ }
+ ac = ac.getParent();
+ }
+ if (doIt) {
+ if (event instanceof ContextRefreshedEvent) {
+ if (getState() != BusState.RUNNING) {
+ initialize();
+ }
+ } else if (event instanceof ContextClosedEvent && getState() == BusState.RUNNING) {
+ // The bus could be create by using SpringBusFactory.createBus("/cxf.xml");
+ // Just to make sure the shutdown is called rightly
+ shutdown();
+ }
+ }
+ }
+
+ @Override
+ public void destroyBeans() {
+ if (closeContext) {
+ ctx.close();
+ }
+ super.destroyBeans();
+ }
+
+ @Override
+ public String getId() {
+ if (id == null) {
+ try {
+ Class<?> clsbc = Class.forName("org.osgi.framework.BundleContext");
+ Class<?> clsb = Class.forName("org.osgi.framework.Bundle");
+ Object o = getExtension(clsbc);
+ Object o2 = clsbc.getMethod("getBundle").invoke(o);
+ String s = (String)clsb.getMethod("getSymbolicName").invoke(o2);
+ id = s + '-' + DEFAULT_BUS_ID + Integer.toString(this.hashCode());
+ } catch (Throwable t) {
+ id = super.getId();
+ }
+ }
+ return id;
+ }
+
+ public void setCloseContext(boolean b) {
+ closeContext = b;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/catalog/CatalogXmlSchemaURIResolver.java b/transform/src/patch/java/org/apache/cxf/catalog/CatalogXmlSchemaURIResolver.java
new file mode 100644
index 0000000..15a3ce2
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/catalog/CatalogXmlSchemaURIResolver.java
@@ -0,0 +1,102 @@
+/**
+ * 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.catalog;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.xml.sax.InputSource;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.resource.ExtendedURIResolver;
+import org.apache.cxf.transport.TransportURIResolver;
+import org.apache.ws.commons.schema.XmlSchemaException;
+import org.apache.ws.commons.schema.resolver.URIResolver;
+
+/**
+ * Resolves URIs using Apache Commons Resolver API.
+ */
+public class CatalogXmlSchemaURIResolver implements URIResolver {
+
+ private ExtendedURIResolver resolver;
+ private Bus bus;
+ private Map<String, String> resolved = new HashMap<>();
+
+ public CatalogXmlSchemaURIResolver(Bus bus) {
+ this.resolver = new TransportURIResolver(bus);
+ this.bus = bus;
+ }
+
+ public Map<String, String> getResolvedMap() {
+ return resolved;
+ }
+
+ public InputSource resolveEntity(String targetNamespace, String schemaLocation, String baseUri) {
+ final String resolvedSchemaLocation;
+ OASISCatalogManager catalogResolver = OASISCatalogManager.getCatalogManager(bus);
+ try {
+ resolvedSchemaLocation = new OASISCatalogManagerHelper().resolve(catalogResolver,
+ schemaLocation, baseUri);
+ } catch (Exception e) {
+ throw new RuntimeException("Catalog resolution failed", e);
+ }
+
+ final InputSource in;
+ if (resolvedSchemaLocation == null) {
+ in = this.resolver.resolve(schemaLocation, baseUri);
+ } else {
+ resolved.put(schemaLocation, resolvedSchemaLocation);
+ in = this.resolver.resolve(resolvedSchemaLocation, baseUri);
+ }
+
+ // If we return null, a NPE is raised in SchemaBuilder.
+ // If we return new InputSource(), a XmlSchemaException is raised
+ // but without any nice error message. So let's just throw a nice error here.
+ if (in == null) {
+ throw new XmlSchemaException("Unable to locate imported document "
+ + "at '" + schemaLocation + "'"
+ + (baseUri == null
+ ? "."
+ : ", relative to '" + baseUri + "'."));
+ } else if (in.getByteStream() != null
+ && !(in.getByteStream() instanceof ByteArrayInputStream)) {
+ //workaround bug in XmlSchema - XmlSchema is not closing the InputStreams
+ //that are returned for imports. Thus, with a lot of services starting up
+ //or a lot of schemas imported or similar, it's easy to run out of
+ //file handles. We'll just load the file into a byte[] and return that.
+ try {
+ InputStream ins = IOUtils.loadIntoBAIS(in.getByteStream());
+ in.setByteStream(ins);
+ } catch (IOException e) {
+ throw new XmlSchemaException("Unable to load imported document "
+ + "at '" + schemaLocation + "'"
+ + (baseUri == null
+ ? "."
+ : ", relative to '" + baseUri + "'."),
+ e);
+ }
+ }
+
+ return in;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/i18n/Exception.java b/transform/src/patch/java/org/apache/cxf/common/i18n/Exception.java
new file mode 100644
index 0000000..fcb38ad
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/i18n/Exception.java
@@ -0,0 +1,58 @@
+/**
+ * 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.common.i18n;
+
+
+
+public class Exception extends java.lang.Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ private final Message message;
+
+ public Exception(Message msg) {
+ message = msg;
+ }
+
+ public Exception(Message msg, Throwable t) {
+ super(t);
+ message = msg;
+ }
+
+ public Exception(Throwable cause) {
+ super(cause);
+ message = null;
+ }
+
+ public String getCode() {
+ if (null != message) {
+ return message.getCode();
+ }
+ return null;
+ }
+
+ @Override
+ public String getMessage() {
+ if (null != message) {
+ return message.toString();
+ }
+ return null;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/i18n/Message.java b/transform/src/patch/java/org/apache/cxf/common/i18n/Message.java
new file mode 100644
index 0000000..d1a65c8
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/i18n/Message.java
@@ -0,0 +1,91 @@
+/**
+ * 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.common.i18n;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.logging.Logger;
+
+public class Message implements Serializable {
+ private static final long serialVersionUID = 42L;
+
+ transient String code;
+ transient Object[] parameters;
+ transient ResourceBundle bundle;
+
+ /**
+ * Constructor.
+ *
+ * @param key the message catalog (resource bundle) key
+ * @param logger a logger with an associated resource bundle
+ * @param params the message substitution parameters
+ */
+ public Message(String key, Logger logger, Object...params) {
+ this(key, logger.getResourceBundle(), params);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param key the message catalog (resource bundle) key
+ * @param catalog the resource bundle
+ * @param params the message substitution parameters
+ */
+ public Message(String key, ResourceBundle catalog, Object...params) {
+ code = key;
+ bundle = catalog;
+ parameters = params;
+ }
+
+ public String toString() {
+ final String fmt;
+ try {
+ if (null == bundle) {
+ return code;
+ }
+ fmt = bundle.getString(code);
+ } catch (MissingResourceException ex) {
+ return code;
+ }
+ return MessageFormat.format(fmt, parameters);
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public Object[] getParameters() {
+ return parameters;
+ }
+
+ private void writeObject(java.io.ObjectOutputStream out)
+ throws IOException {
+ out.writeUTF(toString());
+ }
+ private void readObject(java.io.ObjectInputStream in)
+ throws IOException, ClassNotFoundException {
+ code = in.readUTF();
+ bundle = null;
+ parameters = null;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/i18n/UncheckedException.java b/transform/src/patch/java/org/apache/cxf/common/i18n/UncheckedException.java
new file mode 100644
index 0000000..ea15fd7
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/i18n/UncheckedException.java
@@ -0,0 +1,84 @@
+/**
+ * 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.common.i18n;
+
+import java.util.ResourceBundle;
+import java.util.logging.Logger;
+
+
+
+public class UncheckedException extends java.lang.RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ protected final Message message;
+
+ public UncheckedException(Message msg) {
+ message = msg;
+ }
+
+ public UncheckedException(Message msg, Throwable t) {
+ super(t);
+ message = msg;
+ }
+
+ public UncheckedException(Throwable cause) {
+ super(cause);
+ message = null;
+ }
+
+ public UncheckedException(Logger log, String msg, Object ... params) {
+ message = new org.apache.cxf.common.i18n.Message(msg,
+ log,
+ params);
+ }
+ public UncheckedException(ResourceBundle bundle, String msg, Object ... params) {
+ message = new org.apache.cxf.common.i18n.Message(msg,
+ bundle,
+ params);
+ }
+ public UncheckedException(Logger log, String msg, Throwable t, Object ... params) {
+ super(t);
+ message = new org.apache.cxf.common.i18n.Message(msg,
+ log,
+ params);
+ }
+ public UncheckedException(ResourceBundle bundle, String msg, Throwable t, Object ... params) {
+ super(t);
+ message = new org.apache.cxf.common.i18n.Message(msg,
+ bundle,
+ params);
+ }
+
+ public String getCode() {
+ if (null != message) {
+ return message.getCode();
+ }
+ return null;
+ }
+
+ @Override
+ public String getMessage() {
+ if (null != message) {
+ return message.toString();
+ }
+ return null;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/injection/ResourceInjector.java b/transform/src/patch/java/org/apache/cxf/common/injection/ResourceInjector.java
new file mode 100644
index 0000000..9c420ce
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/injection/ResourceInjector.java
@@ -0,0 +1,446 @@
+/**
+ * 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.common.injection;
+
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.annotation.Resource;
+import javax.annotation.Resources;
+
+
+import org.apache.cxf.common.annotation.AbstractAnnotationVisitor;
+import org.apache.cxf.common.annotation.AnnotationProcessor;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.ClassHelper;
+import org.apache.cxf.common.util.ReflectionUtil;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.resource.ResourceManager;
+import org.apache.cxf.resource.ResourceResolver;
+
+
+/**
+ * injects references specified using @Resource annotation
+ *
+ */
+public class ResourceInjector extends AbstractAnnotationVisitor {
+
+ private static final Logger LOG = LogUtils.getL7dLogger(ResourceInjector.class);
+ private static final List<Class<? extends Annotation>> ANNOTATIONS = new ArrayList<>();
+
+ static {
+ ANNOTATIONS.add(Resource.class);
+ ANNOTATIONS.add(Resources.class);
+ }
+
+
+ private final ResourceManager resourceManager;
+ private final List<ResourceResolver> resourceResolvers;
+
+ public ResourceInjector(ResourceManager resMgr) {
+ this(resMgr, resMgr == null ? null : resMgr.getResourceResolvers());
+ }
+
+ public ResourceInjector(ResourceManager resMgr, List<ResourceResolver> resolvers) {
+ super(ANNOTATIONS);
+ resourceManager = resMgr;
+ resourceResolvers = resolvers;
+ }
+
+ private static Field getField(Class<?> cls, String name) {
+ if (cls == null) {
+ return null;
+ }
+ try {
+ Field f = ReflectionUtil.getDeclaredField(cls, name);
+ if (f == null) {
+ f = getField(cls.getSuperclass(), name);
+ }
+ return f;
+ } catch (Exception ex) {
+ return getField(cls.getSuperclass(), name);
+ }
+ }
+
+ public static boolean processable(Class<?> cls, Object o) {
+ if (cls.getName().startsWith("java.")
+ || cls.getName().startsWith("javax.")) {
+ return false;
+ }
+ NoJSR250Annotations njsr = cls.getAnnotation(NoJSR250Annotations.class);
+ if (njsr != null) {
+ for (String s : njsr.unlessNull()) {
+ try {
+ Field f = getField(cls, s);
+ ReflectionUtil.setAccessible(f);
+ if (f.get(o) == null) {
+ return true;
+ }
+ } catch (Exception ex) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return true;
+ }
+
+ public void inject(Object o) {
+ inject(o, o.getClass());
+ }
+
+ public void inject(Object o, Class<?> claz) {
+ if (processable(claz, o)) {
+ AnnotationProcessor processor = new AnnotationProcessor(o);
+ processor.accept(this, claz);
+ }
+ }
+
+ public void construct(Object o) {
+ setTarget(o);
+ if (processable(targetClass, o)) {
+ invokePostConstruct();
+ }
+ }
+ public void construct(Object o, Class<?> cls) {
+ setTarget(o, cls);
+ if (processable(targetClass, o)) {
+ invokePostConstruct();
+ }
+ }
+
+
+ public void destroy(Object o) {
+ setTarget(o);
+ if (processable(targetClass, o)) {
+ invokePreDestroy();
+ }
+ }
+
+
+ // Implementation of org.apache.cxf.common.annotation.AnnotationVisitor
+
+ @Override
+ public final void visitClass(final Class<?> clz, final Annotation annotation) { //NOPMD
+
+ assert annotation instanceof Resource || annotation instanceof Resources : annotation;
+
+ if (annotation instanceof Resource) {
+ injectResourceClassLevel((Resource)annotation);
+ } else if (annotation instanceof Resources) {
+ Resources resources = (Resources)annotation;
+ for (Resource resource : resources.value()) {
+ injectResourceClassLevel(resource);
+ }
+ }
+
+ }
+
+ private void injectResourceClassLevel(Resource res) {
+ if (res.name() == null || "".equals(res.name())) {
+ LOG.log(Level.INFO, "RESOURCE_NAME_NOT_SPECIFIED", target.getClass().getName());
+ return;
+ }
+
+ Object resource;
+ // first find a setter that matches this resource
+ Method setter = findSetterForResource(res);
+ if (setter != null) {
+ Class<?> type = getResourceType(res, setter);
+ resource = resolveResource(res.name(), type);
+ if (resource == null) {
+ LOG.log(Level.INFO, "RESOURCE_RESOLVE_FAILED");
+ return;
+ }
+
+ invokeSetter(setter, resource);
+ return;
+ }
+
+ Field field = findFieldForResource(res);
+ if (field != null) {
+ Class<?> type = getResourceType(res, field);
+ resource = resolveResource(res.name(), type);
+ if (resource == null) {
+ LOG.log(Level.INFO, "RESOURCE_RESOLVE_FAILED");
+ return;
+ }
+ injectField(field, resource);
+ return;
+ }
+ LOG.log(Level.SEVERE, "NO_SETTER_OR_FIELD_FOR_RESOURCE", getTarget().getClass().getName());
+ }
+
+ public final void visitField(final Field field, final Annotation annotation) {
+
+ assert annotation instanceof Resource : annotation;
+
+ Resource res = (Resource)annotation;
+
+ String name = getFieldNameForResource(res, field);
+ Class<?> type = getResourceType(res, field);
+
+ Object resource = resolveResource(name, type);
+ if (resource == null
+ && "".equals(res.name())) {
+ resource = resolveResource(null, type);
+ }
+ if (resource != null) {
+ injectField(field, resource);
+ } else {
+ LOG.log(Level.INFO, "RESOURCE_RESOLVE_FAILED", name);
+ }
+ }
+
+ public final void visitMethod(final Method method, final Annotation annotation) {
+
+ assert annotation instanceof Resource : annotation;
+
+ Resource res = (Resource)annotation;
+
+ String resourceName = getResourceName(res, method);
+ Class<?> clz = getResourceType(res, method);
+
+ Object resource = resolveResource(resourceName, clz);
+ if (resource == null
+ && "".equals(res.name())) {
+ resource = resolveResource(null, clz);
+ }
+ if (resource != null) {
+ invokeSetter(method, resource);
+ } else {
+ LOG.log(Level.FINE, "RESOURCE_RESOLVE_FAILED", new Object[] {resourceName, clz});
+ }
+ }
+
+ private Field findFieldForResource(Resource res) {
+ assert target != null;
+ assert res.name() != null;
+
+ for (Field field : target.getClass().getFields()) {
+ if (field.getName().equals(res.name())) {
+ return field;
+ }
+ }
+
+ for (Field field : target.getClass().getDeclaredFields()) {
+ if (field.getName().equals(res.name())) {
+ return field;
+ }
+ }
+ return null;
+ }
+
+
+ private Method findSetterForResource(Resource res) {
+ assert target != null;
+
+ String setterName = resourceNameToSetter(res.name());
+ Method setterMethod = null;
+
+ for (Method method : getTarget().getClass().getMethods()) {
+ if (setterName.equals(method.getName())) {
+ setterMethod = method;
+ break;
+ }
+ }
+
+ if (setterMethod != null && setterMethod.getParameterTypes().length != 1) {
+ LOG.log(Level.WARNING, "SETTER_INJECTION_WITH_INCORRECT_TYPE", setterMethod);
+ }
+ return setterMethod;
+ }
+
+ private static String resourceNameToSetter(String resName) {
+ return "set" + StringUtils.capitalize(resName);
+ }
+
+ private void invokeSetter(Method method, Object resource) {
+ try {
+ ReflectionUtil.setAccessible(method);
+ if (method.getDeclaringClass().isAssignableFrom(getTarget().getClass())) {
+ method.invoke(getTarget(), resource);
+ } else { // deal with the proxy setter method
+ Method targetMethod = getTarget().getClass().getMethod(method.getName(),
+ method.getParameterTypes());
+ targetMethod.invoke(getTarget(), resource);
+ }
+ } catch (IllegalAccessException e) {
+ LOG.log(Level.SEVERE, "INJECTION_SETTER_NOT_VISIBLE", method);
+ } catch (InvocationTargetException | SecurityException e) {
+ LogUtils.log(LOG, Level.SEVERE, "INJECTION_SETTER_RAISED_EXCEPTION", e, method);
+ } catch (NoSuchMethodException e) {
+ LOG.log(Level.SEVERE, "INJECTION_SETTER_METHOD_NOT_FOUND", new Object[] {method.getName()});
+ }
+ }
+
+ private String getResourceName(Resource res, Method method) {
+ assert method != null;
+ assert res != null;
+ assert method.getName().startsWith("set") : method;
+
+ if (res.name() == null || res.name().isEmpty()) {
+ String name = method.getName().substring(3);
+ name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
+ return method.getDeclaringClass().getCanonicalName() + '/' + name;
+ }
+ return res.name();
+ }
+
+
+
+ private void injectField(Field field, Object resource) {
+ assert field != null;
+ assert resource != null;
+
+ boolean accessible = field.isAccessible();
+ try {
+ if (field.getType().isAssignableFrom(resource.getClass())) {
+ ReflectionUtil.setAccessible(field);
+ field.set(ClassHelper.getRealObject(getTarget()), resource);
+ }
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ LOG.severe("FAILED_TO_INJECT_FIELD");
+ } finally {
+ ReflectionUtil.setAccessible(field, accessible);
+ }
+ }
+
+
+ public void invokePostConstruct() {
+
+ boolean accessible = false;
+ for (Method method : getPostConstructMethods()) {
+ PostConstruct pc = method.getAnnotation(PostConstruct.class);
+ if (pc != null) {
+ try {
+ ReflectionUtil.setAccessible(method);
+ method.invoke(target);
+ } catch (IllegalAccessException e) {
+ LOG.log(Level.WARNING, "INJECTION_COMPLETE_NOT_VISIBLE", method);
+ } catch (InvocationTargetException e) {
+ LOG.log(Level.WARNING, "INJECTION_COMPLETE_THREW_EXCEPTION", e);
+ } finally {
+ ReflectionUtil.setAccessible(method, accessible);
+ }
+ }
+ }
+ }
+
+ public void invokePreDestroy() {
+
+ boolean accessible = false;
+ for (Method method : getPreDestroyMethods()) {
+ PreDestroy pd = method.getAnnotation(PreDestroy.class);
+ if (pd != null) {
+ try {
+ ReflectionUtil.setAccessible(method);
+ method.invoke(target);
+ } catch (IllegalAccessException e) {
+ LOG.log(Level.WARNING, "PRE_DESTROY_NOT_VISIBLE", method);
+ } catch (InvocationTargetException e) {
+ LOG.log(Level.WARNING, "PRE_DESTROY_THREW_EXCEPTION", e);
+ } finally {
+ ReflectionUtil.setAccessible(method, accessible);
+ }
+ }
+ }
+ }
+
+
+ private Collection<Method> getPostConstructMethods() {
+ return getAnnotatedMethods(PostConstruct.class);
+ }
+
+ private Collection<Method> getPreDestroyMethods() {
+ return getAnnotatedMethods(PreDestroy.class);
+ }
+
+ private Collection<Method> getAnnotatedMethods(Class<? extends Annotation> acls) {
+
+ Collection<Method> methods = new LinkedList<>();
+ addAnnotatedMethods(acls, getTarget().getClass().getMethods(), methods);
+ addAnnotatedMethods(acls, ReflectionUtil.getDeclaredMethods(getTarget().getClass()), methods);
+ if (getTargetClass() != getTarget().getClass()) {
+ addAnnotatedMethods(acls, getTargetClass().getMethods(), methods);
+ addAnnotatedMethods(acls, ReflectionUtil.getDeclaredMethods(getTargetClass()), methods);
+ }
+ return methods;
+ }
+
+ private void addAnnotatedMethods(Class<? extends Annotation> acls, Method[] methods,
+ Collection<Method> annotatedMethods) {
+ for (Method method : methods) {
+ if (method.getAnnotation(acls) != null
+ && !annotatedMethods.contains(method)) {
+ annotatedMethods.add(method);
+ }
+ }
+ }
+
+
+ /**
+ * making this protected to keep pmd happy
+ */
+ protected Class<?> getResourceType(Resource res, Field field) {
+ assert res != null;
+ Class<?> type = res.type();
+ if (res.type() == null || Object.class == res.type()) {
+ type = field.getType();
+ }
+ return type;
+ }
+
+
+ private Class<?> getResourceType(Resource res, Method method) {
+ return res.type() != null && !Object.class.equals(res.type())
+ ? res.type()
+ : method.getParameterTypes()[0];
+ }
+
+
+ private String getFieldNameForResource(Resource res, Field field) {
+ assert res != null;
+ if (res.name() == null || "".equals(res.name())) {
+ return field.getDeclaringClass().getCanonicalName() + "/" + field.getName();
+ }
+ return res.name();
+ }
+
+ private Object resolveResource(String resourceName, Class<?> type) {
+ if (resourceManager == null) {
+ return null;
+ }
+ return resourceManager.resolveResource(resourceName, type, resourceResolvers);
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/jaxb/JAXBUtils.java b/transform/src/patch/java/org/apache/cxf/common/jaxb/JAXBUtils.java
new file mode 100644
index 0000000..f018e2a
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/jaxb/JAXBUtils.java
@@ -0,0 +1,1180 @@
+/**
+ * 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.common.jaxb;
+
+
+import java.io.BufferedReader;
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.StandardCharsets;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.PropertyException;
+import javax.xml.bind.SchemaOutputResolver;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.attachment.AttachmentMarshaller;
+import javax.xml.bind.attachment.AttachmentUnmarshaller;
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.stream.StreamResult;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.spi.ClassLoaderService;
+import org.apache.cxf.common.util.CachedClass;
+import org.apache.cxf.common.util.PackageUtils;
+import org.apache.cxf.common.util.ProxyHelper;
+import org.apache.cxf.common.util.ReflectionInvokationHandler;
+import org.apache.cxf.common.util.ReflectionInvokationHandler.WrapReturn;
+import org.apache.cxf.common.util.ReflectionUtil;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.common.util.SystemPropertyAction;
+import org.apache.cxf.common.xmlschema.SchemaCollection;
+import org.apache.cxf.helpers.JavaUtils;
+
+public final class JAXBUtils {
+ public static final String JAXB_URI = "http://java.sun.com/xml/ns/jaxb";
+
+ private static final Logger LOG = LogUtils.getL7dLogger(JAXBUtils.class);
+
+ public enum IdentifierType {
+ CLASS,
+ INTERFACE,
+ GETTER,
+ SETTER,
+ VARIABLE,
+ CONSTANT
+ };
+
+ private static final char[] XML_NAME_PUNCTUATION_CHARS = new char[] {
+ /* hyphen */ '\u002D',
+ /* period */ '\u002E',
+ /* colon */'\u003A',
+ /* dot */ '\u00B7',
+ /* greek ano teleia */ '\u0387',
+ /* arabic end of ayah */ '\u06DD',
+ /* arabic start of rub el hizb */'\u06DE',
+ /* underscore */ '\u005F',
+ };
+
+ private static final String XML_NAME_PUNCTUATION_STRING = new String(XML_NAME_PUNCTUATION_CHARS);
+
+ private static final Map<String, String> BUILTIN_DATATYPES_MAP;
+ private static final Map<String, Class<?>> HOLDER_TYPES_MAP;
+ private static ClassLoader jaxbXjcLoader;
+ private static volatile Optional<Object> jaxbMinimumEscapeHandler;
+ private static volatile Optional<Object> jaxbNoEscapeHandler;
+
+ static {
+ BUILTIN_DATATYPES_MAP = new HashMap<>();
+ BUILTIN_DATATYPES_MAP.put("string", "java.lang.String");
+ BUILTIN_DATATYPES_MAP.put("integer", "java.math.BigInteger");
+ BUILTIN_DATATYPES_MAP.put("int", "int");
+ BUILTIN_DATATYPES_MAP.put("long", "long");
+ BUILTIN_DATATYPES_MAP.put("short", "short");
+ BUILTIN_DATATYPES_MAP.put("decimal", "java.math.BigDecimal");
+ BUILTIN_DATATYPES_MAP.put("float", "float");
+ BUILTIN_DATATYPES_MAP.put("double", "double");
+ BUILTIN_DATATYPES_MAP.put("boolean", "boolean");
+ BUILTIN_DATATYPES_MAP.put("byte", "byte");
+ BUILTIN_DATATYPES_MAP.put("QName", "javax.xml.namespace.QName");
+ BUILTIN_DATATYPES_MAP.put("dateTime", "javax.xml.datatype.XMLGregorianCalendar");
+ BUILTIN_DATATYPES_MAP.put("base64Binary", "byte[]");
+ BUILTIN_DATATYPES_MAP.put("hexBinary", "byte[]");
+ BUILTIN_DATATYPES_MAP.put("unsignedInt", "long");
+ BUILTIN_DATATYPES_MAP.put("unsignedShort", "short");
+ BUILTIN_DATATYPES_MAP.put("unsignedByte", "byte");
+ BUILTIN_DATATYPES_MAP.put("time", "javax.xml.datatype.XMLGregorianCalendar");
+ BUILTIN_DATATYPES_MAP.put("date", "javax.xml.datatype.XMLGregorianCalendar");
+ BUILTIN_DATATYPES_MAP.put("gYear", "javax.xml.datatype.XMLGregorianCalendar");
+ BUILTIN_DATATYPES_MAP.put("gYearMonth", "javax.xml.datatype.XMLGregorianCalendar");
+ BUILTIN_DATATYPES_MAP.put("gMonth", "javax.xml.datatype.XMLGregorianCalendar");
+ BUILTIN_DATATYPES_MAP.put("gMonthDay", "javax.xml.datatype.XMLGregorianCalendar");
+ BUILTIN_DATATYPES_MAP.put("gDay", "javax.xml.datatype.XMLGregorianCalendar");
+ BUILTIN_DATATYPES_MAP.put("duration", "javax.xml.datatype.Duration");
+ BUILTIN_DATATYPES_MAP.put("NOTATION", "javax.xml.namespace.QName");
+
+ HOLDER_TYPES_MAP = new HashMap<>();
+ HOLDER_TYPES_MAP.put("int", java.lang.Integer.class);
+ HOLDER_TYPES_MAP.put("long", java.lang.Long.class);
+ HOLDER_TYPES_MAP.put("short", java.lang.Short.class);
+ HOLDER_TYPES_MAP.put("float", java.lang.Float.class);
+ HOLDER_TYPES_MAP.put("double", java.lang.Double.class);
+ HOLDER_TYPES_MAP.put("boolean", java.lang.Boolean.class);
+ HOLDER_TYPES_MAP.put("byte", java.lang.Byte.class);
+ }
+
+
+ /**
+ * prevents instantiation
+ *
+ */
+ private JAXBUtils() {
+ }
+
+ public static void closeUnmarshaller(Unmarshaller u) {
+ if (u instanceof Closeable) {
+ //need to do this to clear the ThreadLocal cache
+ //see https://java.net/jira/browse/JAXB-1000
+
+ try {
+ ((Closeable)u).close();
+ } catch (IOException e) {
+ //ignore
+ }
+ }
+ }
+ public static Object unmarshall(JAXBContext c, Element e) throws JAXBException {
+ Unmarshaller u = c.createUnmarshaller();
+ try {
+ u.setEventHandler(null);
+ return u.unmarshal(e);
+ } finally {
+ closeUnmarshaller(u);
+ }
+ }
+ public static <T> JAXBElement<T> unmarshall(JAXBContext c, Element e, Class<T> cls) throws JAXBException {
+ Unmarshaller u = c.createUnmarshaller();
+ try {
+ u.setEventHandler(null);
+ return u.unmarshal(e, cls);
+ } finally {
+ closeUnmarshaller(u);
+ }
+ }
+ public static Object unmarshall(JAXBContext c, Source s) throws JAXBException {
+ Unmarshaller u = c.createUnmarshaller();
+ try {
+ u.setEventHandler(null);
+ return u.unmarshal(s);
+ } finally {
+ closeUnmarshaller(u);
+ }
+ }
+ public static <T> JAXBElement<T> unmarshall(JAXBContext c,
+ XMLStreamReader reader,
+ Class<T> cls) throws JAXBException {
+ Unmarshaller u = c.createUnmarshaller();
+ try {
+ u.setEventHandler(null);
+ return u.unmarshal(reader, cls);
+ } finally {
+ closeUnmarshaller(u);
+ }
+ }
+ public static Object unmarshall(JAXBContext c,
+ XMLStreamReader reader) throws JAXBException {
+ Unmarshaller u = c.createUnmarshaller();
+ try {
+ u.setEventHandler(null);
+ return u.unmarshal(reader);
+ } finally {
+ closeUnmarshaller(u);
+ }
+ }
+
+ public static String builtInTypeToJavaType(String type) {
+ return BUILTIN_DATATYPES_MAP.get(type);
+ }
+
+ public static Class<?> holderClass(String type) {
+ return HOLDER_TYPES_MAP.get(type);
+ }
+
+ /**
+ * Checks if the specified word is a Java keyword (as defined in JavaUtils).
+ *
+ * @param word the word to check.
+ * @return true if the word is a keyword.
+ * @see org.apache.cxf.helpers.JavaUtils
+ */
+ protected static boolean isJavaKeyword(String word) {
+ return JavaUtils.isJavaKeyword(word);
+ }
+
+ /**
+ * Generates a Java package name from a URI according to the
+ * algorithm outlined in JAXB 2.0.
+ *
+ * @param namespaceURI the namespace URI.
+ * @return the package name.
+ */
+ public static String namespaceURIToPackage(String namespaceURI) {
+ try {
+ return nameSpaceURIToPackage(new URI(namespaceURI));
+ } catch (URISyntaxException ex) {
+ return null;
+ }
+ }
+
+ /**
+ * Generates a Java package name from a URI according to the
+ * algorithm outlined in Appendix D of JAXB (2.0+).
+ *
+ * @param uri the namespace URI.
+ * @return the package name.
+ */
+ public static String nameSpaceURIToPackage(URI uri) {
+
+ StringBuilder packageName = new StringBuilder();
+ String authority = uri.getAuthority();
+ String scheme = uri.getScheme();
+ if (authority == null && "urn".equals(scheme)) {
+ authority = uri.getSchemeSpecificPart();
+ }
+
+ if (null != authority && !"".equals(authority)) {
+ if ("urn".equals(scheme)) {
+ packageName.append(authority);
+ /* JAXB 2.2 D.5.1, Rule #5 */
+ for (int i = 0; i < packageName.length(); i++) {
+ if (packageName.charAt(i) == '-') {
+ packageName.setCharAt(i, '.');
+ }
+ }
+ authority = packageName.toString();
+ packageName.setLength(0);
+
+ StringTokenizer st = new StringTokenizer(authority, ":");
+ while (st.hasMoreTokens()) {
+ String token = st.nextToken();
+ if (packageName.length() > 0) {
+ packageName.insert(0, '.');
+ packageName.insert(0, normalizePackageNamePart(token));
+ } else {
+ packageName.insert(0, token);
+ }
+ }
+ authority = packageName.toString();
+ packageName.setLength(0);
+
+ }
+
+ StringTokenizer st = new StringTokenizer(authority, ".");
+ if (st.hasMoreTokens()) {
+ while (st.hasMoreTokens()) {
+ String token = st.nextToken();
+ if (packageName.length() == 0) {
+ if ("www".equals(token)) {
+ continue;
+ }
+ } else {
+ packageName.insert(0, '.');
+ }
+ packageName.insert(0, normalizePackageNamePart(token));
+ }
+ }
+
+ if (!("http".equalsIgnoreCase(scheme) || "urn".equalsIgnoreCase(scheme))) {
+ packageName.insert(0, '.');
+ packageName.insert(0, normalizePackageNamePart(scheme));
+ }
+
+ }
+
+ String path = uri.getPath();
+ if (path == null) {
+ path = "";
+ }
+ /* JAXB 2.2 D.5.1 Rule 2 - remove trailing .??, .???, or .html only. */
+ int index = path.lastIndexOf('.');
+ if (index < 0) {
+ index = path.length();
+ } else {
+ String ending = path.substring(index + 1);
+ if (ending.length() < 2 || (ending.length() > 3
+ && !"html".equalsIgnoreCase(ending))) {
+ index = path.length();
+ }
+ }
+ StringTokenizer st = new StringTokenizer(path.substring(0, index), "/");
+ while (st.hasMoreTokens()) {
+ String token = st.nextToken();
+ if (packageName.length() > 0) {
+ packageName.append('.');
+ }
+ packageName.append(normalizePackageNamePart(token));
+ }
+ return packageName.toString();
+ }
+
+ private static String normalizePackageNamePart(String name) {
+ StringBuilder sname = new StringBuilder(name.toLowerCase());
+
+ for (int i = 0; i < sname.length(); i++) {
+ sname.setCharAt(i, Character.toLowerCase(sname.charAt(i)));
+ }
+
+ for (int i = 0; i < sname.length(); i++) {
+ if (!Character.isJavaIdentifierPart(sname.charAt(i))) {
+ sname.setCharAt(i, '_');
+ }
+ }
+
+ if (isJavaKeyword(sname.toString())) {
+ sname.insert(0, '_');
+ }
+
+ if (!Character.isJavaIdentifierStart(sname.charAt(0))) {
+ sname.insert(0, '_');
+ }
+
+ return sname.toString();
+ }
+
+
+ /**
+ * Converts an XML name to a Java identifier according to the mapping
+ * algorithm outlined in the JAXB specification
+ *
+ * @param name the XML name
+ * @return the Java identifier
+ */
+ public static String nameToIdentifier(String name, IdentifierType type) {
+
+ if (null == name || name.length() == 0) {
+ return name;
+ }
+
+ // algorithm will not change an XML name that is already a legal and
+ // conventional (!) Java class, method, or constant identifier
+
+ StringBuilder buf = new StringBuilder(name);
+ boolean hasUnderscore = false;
+ boolean legalIdentifier = Character.isJavaIdentifierStart(buf.charAt(0));
+
+ for (int i = 1; i < name.length() && legalIdentifier; i++) {
+ legalIdentifier &= Character.isJavaIdentifierPart(buf.charAt(i));
+ hasUnderscore |= '_' == buf.charAt(i);
+ }
+
+ boolean conventionalIdentifier = isConventionalIdentifier(buf, type);
+ if (legalIdentifier && conventionalIdentifier) {
+ if (JAXBUtils.isJavaKeyword(name) && type == IdentifierType.VARIABLE) {
+ name = normalizePackageNamePart(name);
+ }
+ if (!hasUnderscore || IdentifierType.CLASS != type) {
+ return name;
+ }
+ }
+
+ // split into words
+
+ List<String> words = new ArrayList<>();
+
+ StringTokenizer st = new StringTokenizer(name, XML_NAME_PUNCTUATION_STRING);
+ while (st.hasMoreTokens()) {
+ words.add(st.nextToken());
+ }
+
+ for (int i = 0; i < words.size(); i++) {
+ splitWord(words, i);
+ }
+
+ return makeConventionalIdentifier(words, type);
+ }
+
+ private static void splitWord(List<String> words, int listIndex) {
+ String word = words.get(listIndex);
+ if (word.length() <= 1) {
+ return;
+ }
+ int index = listIndex + 1;
+ StringBuilder sword = new StringBuilder(word);
+ int first = 0;
+ char firstChar = sword.charAt(first);
+ if (Character.isLowerCase(firstChar)) {
+ sword.setCharAt(first, Character.toUpperCase(firstChar));
+ }
+ int i = 1;
+
+ while (i < sword.length()) {
+ if (Character.isDigit(firstChar)) {
+ while (i < sword.length() && Character.isDigit(sword.charAt(i))) {
+ i++;
+ }
+ } else if (isCasedLetter(firstChar)) {
+ boolean previousIsLower = Character.isLowerCase(firstChar);
+ while (i < sword.length() && isCasedLetter(sword.charAt(i))) {
+ if (Character.isUpperCase(sword.charAt(i)) && previousIsLower) {
+ break;
+ }
+ previousIsLower = Character.isLowerCase(sword.charAt(i));
+ i++;
+ }
+ } else {
+ // first must be a mark or an uncased letter
+ while (i < sword.length() && (isMark(sword.charAt(i)) || !isCasedLetter(sword.charAt(i)))) {
+ i++;
+ }
+ }
+
+ // characters from first to i are all either
+ // * digits
+ // * upper or lower case letters, with only the first one an upper
+ // * uncased letters or marks
+
+
+ String newWord = sword.substring(first, i);
+ words.add(index, newWord);
+ index++;
+ if (i >= sword.length()) {
+ break;
+ }
+ first = i;
+ firstChar = sword.charAt(first);
+ }
+
+ if (index > (listIndex + 1)) {
+ words.remove(listIndex);
+ }
+ }
+
+ private static boolean isMark(char c) {
+ return Character.isJavaIdentifierPart(c) && !Character.isLetter(c) && !Character.isDigit(c);
+ }
+
+ private static boolean isCasedLetter(char c) {
+ return Character.isUpperCase(c) || Character.isLowerCase(c);
+ }
+
+ private static boolean isConventionalIdentifier(StringBuilder buf, IdentifierType type) {
+ if (null == buf || buf.length() == 0) {
+ return false;
+ }
+ final boolean result;
+ if (IdentifierType.CONSTANT == type) {
+ for (int i = 0; i < buf.length(); i++) {
+ if (Character.isLowerCase(buf.charAt(i))) {
+ return false;
+ }
+ }
+ result = true;
+ } else if (IdentifierType.VARIABLE == type) {
+ result = Character.isLowerCase(buf.charAt(0));
+ } else {
+ int pos = 3;
+ if (IdentifierType.GETTER == type
+ && !(buf.length() >= pos
+ && "get".equals(buf.subSequence(0, 3)))) {
+ return false;
+ } else if (IdentifierType.SETTER == type
+ && !(buf.length() >= pos && "set".equals(buf.subSequence(0, 3)))) {
+ return false;
+ } else {
+ pos = 0;
+ }
+ result = Character.isUpperCase(buf.charAt(pos));
+ }
+ return result;
+ }
+
+ private static String makeConventionalIdentifier(List<String> words, IdentifierType type) {
+ StringBuilder buf = new StringBuilder();
+ boolean firstWord = true;
+ if (IdentifierType.GETTER == type) {
+ buf.append("get");
+ } else if (IdentifierType.SETTER == type) {
+ buf.append("set");
+ }
+ for (String w : words) {
+ int l = buf.length();
+ if (l > 0 && IdentifierType.CONSTANT == type) {
+ buf.append('_');
+ l++;
+ }
+ buf.append(w);
+ if (IdentifierType.CONSTANT == type) {
+ for (int i = l; i < buf.length(); i++) {
+ if (Character.isLowerCase(buf.charAt(i))) {
+ buf.setCharAt(i, Character.toUpperCase(buf.charAt(i)));
+ }
+ }
+ } else if (IdentifierType.VARIABLE == type) {
+ if (firstWord && Character.isUpperCase(buf.charAt(l))) {
+ buf.setCharAt(l, Character.toLowerCase(buf.charAt(l)));
+ }
+ } else {
+ if (firstWord && Character.isLowerCase(buf.charAt(l))) {
+ buf.setCharAt(l, Character.toUpperCase(buf.charAt(l)));
+ }
+ }
+ firstWord = false;
+ }
+ return buf.toString();
+ }
+
+ public static Class<?> getValidClass(Class<?> cls) {
+ if (cls.isEnum() || cls.isArray()) {
+ return cls;
+ }
+
+ if (cls == Object.class || cls == String.class || cls.isPrimitive() || cls.isAnnotation()
+ || "javax.xml.ws.Holder".equals(cls.getName())) {
+ return null;
+ } else if (cls.isInterface()
+ || "javax.xml.ws.wsaddressing.W3CEndpointReference".equals(cls.getName())) {
+ return cls;
+ }
+
+ Constructor<?> cons = ReflectionUtil.getDeclaredConstructor(cls);
+ if (cons == null) {
+ cons = ReflectionUtil.getConstructor(cls);
+ if (cons == null) {
+ return null;
+ }
+ }
+ return cls;
+ }
+
+ private static synchronized ClassLoader getXJCClassLoader() {
+ if (jaxbXjcLoader == null) {
+ try {
+ Class.forName("com.sun.tools.internal.xjc.api.XJC");
+ jaxbXjcLoader = ClassLoader.getSystemClassLoader();
+ } catch (Exception t2) {
+ //couldn't find either, probably cause tools.jar isn't on
+ //the classpath. Let's see if we can find the tools jar
+ String s = SystemPropertyAction.getProperty("java.home");
+ if (!StringUtils.isEmpty(s)) {
+ File home = new File(s);
+ File jar = new File(home, "lib/tools.jar");
+ if (!jar.exists()) {
+ jar = new File(home, "../lib/tools.jar");
+ }
+ if (jar.exists()) {
+ try {
+ jaxbXjcLoader = new URLClassLoader(new URL[] {jar.toURI().toURL()});
+ Class.forName("com.sun.tools.internal.xjc.api.XJC", false, jaxbXjcLoader);
+ } catch (Exception e) {
+ jaxbXjcLoader = null;
+ }
+ }
+ }
+ }
+ }
+ return jaxbXjcLoader;
+ }
+
+ public static Object setNamespaceMapper(Bus bus, final Map<String, String> nspref,
+ Marshaller marshaller) throws PropertyException {
+ ClassLoaderService classLoaderService = bus.getExtension(ClassLoaderService.class);
+ Object mapper = classLoaderService.createNamespaceWrapperInstance(marshaller.getClass(), nspref);
+ if (mapper != null) {
+ if (marshaller.getClass().getName().contains(".internal.")) {
+ marshaller.setProperty("com.sun.xml.internal.bind.namespacePrefixMapper",
+ mapper);
+ } else if (marshaller.getClass().getName().contains("com.sun")) {
+ marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper",
+ mapper);
+ } else if (marshaller.getClass().getName().contains("eclipse")) {
+ marshaller.setProperty("eclipselink.namespace-prefix-mapper",
+ mapper);
+ }
+ }
+ return mapper;
+ }
+ public static BridgeWrapper createBridge(Set<Class<?>> ctxClasses,
+ QName qname,
+ Class<?> refcls,
+ Annotation[] anns) throws JAXBException {
+ try {
+ Class<?> cls;
+ Class<?> refClass;
+ String pkg = "com.sun.xml.bind.";
+ try {
+ cls = Class.forName("com.sun.xml.bind.api.JAXBRIContext");
+ refClass = Class.forName(pkg + "api.TypeReference");
+ } catch (ClassNotFoundException e) {
+ cls = Class.forName("com.sun.xml.internal.bind.api.JAXBRIContext", true, getXJCClassLoader());
+ pkg = "com.sun.xml.internal.bind.";
+ refClass = Class.forName(pkg + "api.TypeReference", true, getXJCClassLoader());
+ }
+ Object ref = refClass.getConstructor(QName.class,
+ Type.class,
+ anns.getClass()).newInstance(qname, refcls, anns);
+ List<Object> typeRefs = new ArrayList<>();
+ typeRefs.add(ref);
+ List<Class<?>> clses = new ArrayList<>(ctxClasses);
+ clses.add(refClass.getField("type").get(ref).getClass());
+ if (!refcls.isInterface()) {
+ clses.add(refcls);
+ }
+
+ Object ctx = null;
+ for (Method m : cls.getDeclaredMethods()) {
+ if ("newInstance".equals(m.getName())
+ && m.getParameterTypes().length == 6) {
+ ctx = m.invoke(null, clses.toArray(new Class<?>[0]),
+ typeRefs, null, null, true, null);
+
+ }
+ }
+
+ if (ctx == null) {
+ throw new JAXBException("No ctx found");
+ }
+
+ Object bridge = ctx.getClass().getMethod("createBridge", refClass).invoke(ctx, ref);
+ return ReflectionInvokationHandler.createProxyWrapper(bridge,
+ BridgeWrapper.class);
+ } catch (Exception ex) {
+ throw new JAXBException(ex);
+ }
+ }
+ public interface BridgeWrapper {
+
+ Object unmarshal(XMLStreamReader source, AttachmentUnmarshaller am) throws JAXBException;
+
+ Object unmarshal(InputStream source) throws JAXBException;
+
+ Object unmarshal(Node source, AttachmentUnmarshaller am) throws JAXBException;
+
+ void marshal(Object elValue, XMLStreamWriter source, AttachmentMarshaller m) throws JAXBException;
+
+ void marshal(Object elValue, StreamResult s1) throws JAXBException;
+
+ void marshal(Object elValue, Node source, AttachmentMarshaller am) throws JAXBException;
+ }
+
+
+ public static SchemaCompiler createSchemaCompiler() throws JAXBException {
+ try {
+ Class<?> cls;
+ Object sc;
+ try {
+ cls = Class.forName("com.sun.tools.xjc.api.XJC");
+ sc = cls.getMethod("createSchemaCompiler").invoke(null);
+ } catch (Throwable e) {
+ cls = Class.forName("com.sun.tools.internal.xjc.api.XJC", true, getXJCClassLoader());
+ sc = cls.getMethod("createSchemaCompiler").invoke(null);
+ }
+
+ return ReflectionInvokationHandler.createProxyWrapper(sc,
+ SchemaCompiler.class);
+ } catch (Exception ex) {
+ throw new JAXBException(ex);
+ }
+ }
+
+ public static SchemaCompiler createSchemaCompilerWithDefaultAllocator(Set<String> allocatorSet) {
+
+ try {
+ SchemaCompiler compiler = JAXBUtils.createSchemaCompiler();
+ Object allocator = ReflectionInvokationHandler
+ .createProxyWrapper(new DefaultClassNameAllocator(allocatorSet),
+ JAXBUtils.getParamClass(compiler, "setClassNameAllocator"));
+
+ compiler.setClassNameAllocator(allocator);
+ return compiler;
+ } catch (JAXBException e1) {
+ throw new IllegalStateException("Unable to create schema compiler", e1);
+ }
+
+ }
+
+ public static void logGeneratedClassNames(Logger logger, JCodeModel codeModel) {
+ if (!logger.isLoggable(Level.FINE)) {
+ return;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for (Iterator<JPackage> itr = codeModel.packages(); itr.hasNext();) {
+ JPackage package1 = itr.next();
+
+ for (Iterator<JDefinedClass> citr = package1.classes(); citr.hasNext();) {
+ if (!first) {
+ sb.append(", ");
+ } else {
+ first = false;
+ }
+ sb.append(citr.next().fullName());
+ }
+ }
+
+ logger.log(Level.FINE, "Created classes: " + sb.toString());
+ }
+
+ public static List<String> getGeneratedClassNames(JCodeModel codeModel) {
+ List<String> classes = new ArrayList<>();
+ for (Iterator<JPackage> itr = codeModel.packages(); itr.hasNext();) {
+ JPackage package1 = itr.next();
+
+ for (Iterator<JDefinedClass> citr = package1.classes(); citr.hasNext();) {
+ classes.add(citr.next().fullName());
+ }
+ }
+ return classes;
+ }
+ public static Object createFileCodeWriter(File f) throws JAXBException {
+ return createFileCodeWriter(f, StandardCharsets.UTF_8.name());
+ }
+ public static Object createFileCodeWriter(File f, String encoding) throws JAXBException {
+ try {
+ Class<?> cls;
+ try {
+ cls = Class.forName("com.sun.codemodel.writer.FileCodeWriter");
+ } catch (ClassNotFoundException e) {
+ cls = Class.forName("com.sun.codemodel.internal.writer.FileCodeWriter",
+ true, getXJCClassLoader());
+ }
+ if (encoding != null) {
+ try {
+ return cls.getConstructor(File.class, String.class)
+ .newInstance(f, encoding);
+ } catch (Exception ex) {
+ // try a single argument constructor
+ }
+ }
+ return cls.getConstructor(File.class).newInstance(f);
+ } catch (Exception ex) {
+ throw new JAXBException(ex);
+ }
+ }
+
+ public static Class<?> getParamClass(SchemaCompiler sc, String method) {
+ Object o = ((ReflectionInvokationHandler)Proxy.getInvocationHandler(sc)).getTarget();
+ for (Method m : o.getClass().getMethods()) {
+ if (m.getName().equals(method) && m.getParameterTypes().length == 1) {
+ return m.getParameterTypes()[0];
+ }
+ }
+ return null;
+ }
+
+
+ public static List<DOMResult> generateJaxbSchemas(
+ JAXBContext context, final Map<String, DOMResult> builtIns) throws IOException {
+ final List<DOMResult> results = new ArrayList<>();
+
+ context.generateSchema(new SchemaOutputResolver() {
+ @Override
+ public Result createOutput(String ns, String file) throws IOException {
+ DOMResult result = new DOMResult();
+
+ if (builtIns.containsKey(ns)) {
+ DOMResult dr = builtIns.get(ns);
+ result.setSystemId(dr.getSystemId());
+ results.add(dr);
+ return result;
+ }
+ result.setSystemId(file);
+ results.add(result);
+ return result;
+ }
+ });
+ return results;
+ }
+
+ public static String getPackageNamespace(Class<?> cls) {
+ Package p = cls.getPackage();
+ if (p != null) {
+ javax.xml.bind.annotation.XmlSchema schemaAnn =
+ p.getAnnotation(javax.xml.bind.annotation.XmlSchema.class);
+ if (schemaAnn != null) {
+ return schemaAnn.namespace();
+ }
+ }
+ return null;
+ }
+
+ public static void scanPackages(Set<Class<?>> classes,
+ Map<Package, CachedClass> objectFactoryCache) {
+ scanPackages(classes, null, objectFactoryCache);
+ }
+ public static void scanPackages(Set<Class<?>> classes,
+ Class<?>[] extraClass,
+ Map<Package, CachedClass> objectFactoryCache) {
+
+ // add user extra class into jaxb context
+ if (extraClass != null && extraClass.length > 0) {
+ for (Class<?> clz : extraClass) {
+ classes.add(clz);
+ }
+ }
+
+ // try and read any jaxb.index files that are with the other classes.
+ // This should
+ // allow loading of extra classes (such as subclasses for inheritance
+ // reasons)
+ // that are in the same package. Also check for ObjectFactory classes
+ Map<String, InputStream> packages = new HashMap<>();
+ Map<String, ClassLoader> packageLoaders = new HashMap<>();
+ Set<Class<?>> objectFactories = new HashSet<>();
+ for (Class<?> jcls : classes) {
+ String pkgName = PackageUtils.getPackageName(jcls);
+ if (!packages.containsKey(pkgName)) {
+ Package pkg = jcls.getPackage();
+
+ packages.put(pkgName, jcls.getResourceAsStream("jaxb.index"));
+ packageLoaders.put(pkgName, getClassLoader(jcls));
+ String objectFactoryClassName = pkgName + "." + "ObjectFactory";
+ Class<?> ofactory = null;
+ CachedClass cachedFactory = null;
+ if (pkg != null && objectFactoryCache != null) {
+ synchronized (objectFactoryCache) {
+ cachedFactory = objectFactoryCache.get(pkg);
+ }
+ }
+ if (cachedFactory != null) {
+ ofactory = cachedFactory.getCachedClass();
+ }
+ if (ofactory == null) {
+ try {
+ ofactory = Class.forName(objectFactoryClassName, false, getClassLoader(jcls));
+ objectFactories.add(ofactory);
+ addToObjectFactoryCache(pkg, ofactory, objectFactoryCache);
+ } catch (ClassNotFoundException e) {
+ addToObjectFactoryCache(pkg, null, objectFactoryCache);
+ }
+ } else {
+ objectFactories.add(ofactory);
+ }
+ }
+ }
+ for (Map.Entry<String, InputStream> entry : packages.entrySet()) {
+ if (entry.getValue() != null) {
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(entry.getValue(), StandardCharsets.UTF_8))) {
+ String pkg = entry.getKey();
+ ClassLoader loader = packageLoaders.get(pkg);
+ if (!StringUtils.isEmpty(pkg)) {
+ pkg += ".";
+ }
+
+ String line = reader.readLine();
+ while (line != null) {
+ line = line.trim();
+ if (line.indexOf('#') != -1) {
+ line = line.substring(0, line.indexOf('#'));
+ }
+ if (!StringUtils.isEmpty(line)) {
+ try {
+ Class<?> ncls = Class.forName(pkg + line, false, loader);
+ classes.add(ncls);
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ line = reader.readLine();
+ }
+ } catch (IOException e) {
+ // ignore
+ } finally {
+ try {
+ entry.getValue().close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ }
+ classes.addAll(objectFactories);
+ }
+
+ private static ClassLoader getClassLoader(final Class<?> clazz) {
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+ @Override
+ public ClassLoader run() {
+ return clazz.getClassLoader();
+ }
+ });
+ }
+ return clazz.getClassLoader();
+ }
+
+ private static void addToObjectFactoryCache(Package objectFactoryPkg,
+ Class<?> ofactory,
+ Map<Package, CachedClass> objectFactoryCache) {
+ if (objectFactoryPkg == null || objectFactoryCache == null) {
+ return;
+ }
+ synchronized (objectFactoryCache) {
+ objectFactoryCache.put(objectFactoryPkg,
+ new CachedClass(ofactory));
+ }
+ }
+
+ public static class DefaultClassNameAllocator {
+ private final Set<String> typesClassNames;
+
+ public DefaultClassNameAllocator() {
+ this(new HashSet<>());
+ }
+
+ public DefaultClassNameAllocator(Set<String> set) {
+ typesClassNames = set;
+ }
+
+ public String assignClassName(String packageName, String className) {
+ String fullClassName = className;
+ String fullPckClass = packageName + "." + fullClassName;
+ int cnt = 0;
+ while (typesClassNames.contains(fullPckClass)) {
+ cnt++;
+ fullClassName = className + cnt;
+ fullPckClass = packageName + "." + fullClassName;
+ }
+ typesClassNames.add(fullPckClass);
+ return fullClassName;
+ }
+
+ }
+
+ public interface SchemaCompiler {
+ void setEntityResolver(EntityResolver entityResolver);
+
+ void setErrorListener(Object elForRun);
+
+ void setClassNameAllocator(Object allocator);
+
+ @WrapReturn(S2JJAXBModel.class)
+ S2JJAXBModel bind();
+
+ void parseSchema(InputSource source);
+
+ void parseSchema(String key, Element el);
+ void parseSchema(String key, XMLStreamReader el);
+
+ @WrapReturn(Options.class)
+ Options getOptions();
+ }
+ public interface S2JJAXBModel {
+
+ @WrapReturn(JCodeModel.class)
+ JCodeModel generateCode(Object object, Object elForRun);
+
+ @WrapReturn(Mapping.class)
+ Mapping get(QName qn);
+
+ @WrapReturn(TypeAndAnnotation.class)
+ TypeAndAnnotation getJavaType(QName typeQName);
+ }
+ public interface Mapping {
+ @WrapReturn(TypeAndAnnotation.class)
+ TypeAndAnnotation getType();
+ }
+ public interface TypeAndAnnotation {
+ @WrapReturn(JType.class)
+ JType getTypeClass();
+ }
+ public interface JType {
+ boolean isArray();
+
+ @WrapReturn(JType.class)
+ JType elementType();
+
+ boolean isPrimitive();
+
+ String binaryName();
+
+ String fullName();
+
+ String name();
+
+ @WrapReturn(value = JType.class, iterator = true)
+ Iterator<JType> classes();
+ }
+ public interface Options {
+
+ void addGrammar(InputSource is);
+
+ void addBindFile(InputSource is);
+
+ void parseArguments(String[] args);
+
+ String getBuildID();
+ }
+ public interface JCodeModel {
+
+ void build(Object writer) throws IOException;
+
+ @WrapReturn(value = JPackage.class, iterator = true)
+ Iterator<JPackage> packages();
+ }
+ public interface JPackage {
+
+ String name();
+
+ @WrapReturn(value = JDefinedClass.class, iterator = true)
+ Iterator<JDefinedClass> classes();
+ }
+ public interface JDefinedClass {
+ String name();
+
+ String fullName();
+ }
+
+ public static boolean isJAXB22() {
+ Target t = XmlElement.class.getAnnotation(Target.class);
+ //JAXB 2.2 allows XmlElement on params.
+ for (ElementType et : t.value()) {
+ if (et == ElementType.PARAMETER) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static JAXBContextProxy createJAXBContextProxy(final JAXBContext ctx) {
+ return createJAXBContextProxy(ctx, null, null);
+ }
+ public static JAXBContextProxy createJAXBContextProxy(final JAXBContext ctx,
+ final SchemaCollection collection,
+ final String defaultNs) {
+ if (ctx.getClass().getName().contains("com.sun.")
+ || collection == null) {
+ return ReflectionInvokationHandler.createProxyWrapper(ctx, JAXBContextProxy.class);
+ }
+ return new SchemaCollectionContextProxy(ctx, collection, defaultNs);
+ }
+ public static JAXBBeanInfo getBeanInfo(JAXBContextProxy context, Class<?> cls) {
+ Object o = context.getBeanInfo(cls);
+ if (o == null) {
+ return null;
+ }
+ if (o instanceof JAXBBeanInfo) {
+ return (JAXBBeanInfo)o;
+ }
+ return ReflectionInvokationHandler.createProxyWrapper(o, JAXBBeanInfo.class);
+ }
+
+ private static String getPostfix(Class<?> cls) {
+ String className = cls.getName();
+ if (className.contains("com.sun.xml.internal")
+ || className.contains("eclipse")) {
+ //eclipse moxy accepts sun package CharacterEscapeHandler
+ return ".internal";
+ } else if (className.contains("com.sun.xml.bind")
+ || className.startsWith("com.ibm.xml")) {
+ return "";
+ }
+ return null;
+ }
+
+ public static void setMinimumEscapeHandler(Marshaller marshaller) {
+ if (jaxbMinimumEscapeHandler == null) {
+ jaxbMinimumEscapeHandler = Optional.ofNullable(createMininumEscapeHandler(marshaller.getClass()));
+ }
+ jaxbMinimumEscapeHandler.ifPresent(p -> setEscapeHandler(marshaller, p));
+ }
+
+ public static void setNoEscapeHandler(final Marshaller marshaller) {
+ if (jaxbNoEscapeHandler == null) {
+ jaxbNoEscapeHandler = Optional.ofNullable(createNoEscapeHandler(marshaller.getClass()));
+ }
+ jaxbNoEscapeHandler.ifPresent(p -> setEscapeHandler(marshaller, p));
+ }
+
+ public static void setEscapeHandler(Marshaller marshaller, Object escapeHandler) {
+ try {
+ String postFix = getPostfix(marshaller.getClass());
+ if (postFix != null && escapeHandler != null) {
+ marshaller.setProperty("com.sun.xml" + postFix + ".bind.characterEscapeHandler", escapeHandler);
+ }
+ } catch (PropertyException e) {
+ LOG.log(Level.INFO, "Failed to set MinumEscapeHandler to jaxb marshaller", e);
+ }
+ }
+
+ public static Object createMininumEscapeHandler(Class<?> cls) {
+ return createEscapeHandler(cls, "MinimumEscapeHandler");
+ }
+
+ public static Object createNoEscapeHandler(Class<?> cls) {
+ return createEscapeHandler(cls, "NoEscapeHandler");
+ }
+
+ private static Object createEscapeHandler(Class<?> cls, String simpleClassName) {
+ try {
+ String postFix = getPostfix(cls);
+ if (postFix == null) {
+ LOG.log(Level.WARNING, "Failed to create" + simpleClassName + " for unknown jaxb class:"
+ + cls);
+ return null;
+ }
+ Class<?> handlerClass = ClassLoaderUtils.loadClass("com.sun.xml" + postFix
+ + ".bind.marshaller." + simpleClassName,
+ cls);
+ Class<?> handlerInterface = ClassLoaderUtils
+ .loadClass("com.sun.xml" + postFix + ".bind.marshaller.CharacterEscapeHandler",
+ cls);
+ Object targetHandler = ReflectionUtil.getDeclaredField(handlerClass, "theInstance").get(null);
+ return ProxyHelper.getProxy(cls.getClassLoader(),
+ new Class[] {handlerInterface},
+ new EscapeHandlerInvocationHandler(targetHandler));
+ } catch (Exception e) {
+ if ("NoEscapeHandler".equals(simpleClassName)) {
+ //this class doesn't exist in JAXB 2.2 so expected
+ LOG.log(Level.FINER, "Failed to create " + simpleClassName);
+ } else {
+ LOG.log(Level.INFO, "Failed to create " + simpleClassName);
+ }
+ }
+ return null;
+ }
+
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/logging/AbstractDelegatingLogger.java b/transform/src/patch/java/org/apache/cxf/common/logging/AbstractDelegatingLogger.java
new file mode 100644
index 0000000..dd3cbd5
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/logging/AbstractDelegatingLogger.java
@@ -0,0 +1,457 @@
+/**
+ * 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.common.logging;
+
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.logging.Filter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+/**
+ * java.util.logging.Logger implementation delegating to another framework.
+ * All methods can be used except:
+ * setLevel
+ * addHandler / getHandlers
+ * setParent / getParent
+ * setUseParentHandlers / getUseParentHandlers
+ */
+public abstract class AbstractDelegatingLogger extends Logger {
+
+ protected AbstractDelegatingLogger(String name, String resourceBundleName) {
+ super(name, resourceBundleName);
+ }
+
+ @Override
+ public void log(LogRecord record) {
+ if (isLoggable(record.getLevel())) {
+ doLog(record);
+ }
+ }
+
+ @Override
+ public void log(Level level, String msg) {
+ if (isLoggable(level)) {
+ LogRecord lr = new LogRecord(level, msg);
+ doLog(lr);
+ }
+ }
+
+ @Override
+ public void log(Level level, String msg, Object param1) {
+ if (isLoggable(level)) {
+ LogRecord lr = new LogRecord(level, msg);
+ Object[] params = {param1 };
+ lr.setParameters(params);
+ doLog(lr);
+ }
+ }
+
+ @Override
+ public void log(Level level, String msg, Object[] params) {
+ if (isLoggable(level)) {
+ LogRecord lr = new LogRecord(level, msg);
+ lr.setParameters(params);
+ doLog(lr);
+ }
+ }
+
+ @Override
+ public void log(Level level, String msg, Throwable thrown) {
+ if (isLoggable(level)) {
+ LogRecord lr = new LogRecord(level, msg);
+ lr.setThrown(thrown);
+ doLog(lr);
+ }
+ }
+
+ @Override
+ public void logp(Level level, String sourceClass, String sourceMethod, String msg) {
+ if (isLoggable(level)) {
+ LogRecord lr = new LogRecord(level, msg);
+ lr.setSourceClassName(sourceClass);
+ lr.setSourceMethodName(sourceMethod);
+ doLog(lr);
+ }
+ }
+
+ @Override
+ public void logp(Level level, String sourceClass, String sourceMethod, String msg, Object param1) {
+ if (isLoggable(level)) {
+ LogRecord lr = new LogRecord(level, msg);
+ lr.setSourceClassName(sourceClass);
+ lr.setSourceMethodName(sourceMethod);
+ Object[] params = {param1 };
+ lr.setParameters(params);
+ doLog(lr);
+ }
+ }
+
+ @Override
+ public void logp(Level level, String sourceClass, String sourceMethod, String msg, Object[] params) {
+ if (isLoggable(level)) {
+ LogRecord lr = new LogRecord(level, msg);
+ lr.setSourceClassName(sourceClass);
+ lr.setSourceMethodName(sourceMethod);
+ lr.setParameters(params);
+ doLog(lr);
+ }
+ }
+
+ @Override
+ public void logp(Level level, String sourceClass, String sourceMethod, String msg, Throwable thrown) {
+ if (isLoggable(level)) {
+ LogRecord lr = new LogRecord(level, msg);
+ lr.setSourceClassName(sourceClass);
+ lr.setSourceMethodName(sourceMethod);
+ lr.setThrown(thrown);
+ doLog(lr);
+ }
+ }
+
+
+ @Override
+ @Deprecated
+ public void logrb(Level level, String sourceClass, String sourceMethod, String bundleName, String msg) {
+ if (isLoggable(level)) {
+ LogRecord lr = new LogRecord(level, msg);
+ lr.setSourceClassName(sourceClass);
+ lr.setSourceMethodName(sourceMethod);
+ doLog(lr, bundleName);
+ }
+ }
+
+
+ @Override
+ @Deprecated
+ public void logrb(Level level, String sourceClass, String sourceMethod,
+ String bundleName, String msg, Object param1) {
+ if (isLoggable(level)) {
+ LogRecord lr = new LogRecord(level, msg);
+ lr.setSourceClassName(sourceClass);
+ lr.setSourceMethodName(sourceMethod);
+ Object[] params = {param1 };
+ lr.setParameters(params);
+ doLog(lr, bundleName);
+ }
+ }
+
+
+ @Override
+ @Deprecated
+ public void logrb(Level level, String sourceClass, String sourceMethod,
+ String bundleName, String msg, Object[] params) {
+ if (isLoggable(level)) {
+ LogRecord lr = new LogRecord(level, msg);
+ lr.setSourceClassName(sourceClass);
+ lr.setSourceMethodName(sourceMethod);
+ lr.setParameters(params);
+ doLog(lr, bundleName);
+ }
+ }
+
+ @Override
+ @Deprecated
+ public void logrb(Level level, String sourceClass, String sourceMethod,
+ String bundleName, String msg, Throwable thrown) {
+ if (isLoggable(level)) {
+ LogRecord lr = new LogRecord(level, msg);
+ lr.setSourceClassName(sourceClass);
+ lr.setSourceMethodName(sourceMethod);
+ lr.setThrown(thrown);
+ doLog(lr, bundleName);
+ }
+ }
+
+ @Override
+ public void entering(String sourceClass, String sourceMethod) {
+ if (isLoggable(Level.FINER)) {
+ logp(Level.FINER, sourceClass, sourceMethod, "ENTRY");
+ }
+ }
+
+ @Override
+ public void entering(String sourceClass, String sourceMethod, Object param1) {
+ if (isLoggable(Level.FINER)) {
+ Object[] params = {param1 };
+ logp(Level.FINER, sourceClass, sourceMethod, "ENTRY {0}", params);
+ }
+ }
+
+ @Override
+ public void entering(String sourceClass, String sourceMethod, Object[] params) {
+ if (isLoggable(Level.FINER)) {
+ String msg = "ENTRY";
+ if (params == null) {
+ logp(Level.FINER, sourceClass, sourceMethod, msg);
+ return;
+ }
+ StringBuilder builder = new StringBuilder(msg);
+ for (int i = 0; i < params.length; i++) {
+ builder.append(" {");
+ builder.append(Integer.toString(i));
+ builder.append('}');
+ }
+ logp(Level.FINER, sourceClass, sourceMethod, builder.toString(), params);
+ }
+ }
+
+ @Override
+ public void exiting(String sourceClass, String sourceMethod) {
+ if (isLoggable(Level.FINER)) {
+ logp(Level.FINER, sourceClass, sourceMethod, "RETURN");
+ }
+ }
+
+ @Override
+ public void exiting(String sourceClass, String sourceMethod, Object result) {
+ if (isLoggable(Level.FINER)) {
+ Object[] params = {result };
+ logp(Level.FINER, sourceClass, sourceMethod, "RETURN {0}", params);
+ }
+ }
+
+ @Override
+ public void throwing(String sourceClass, String sourceMethod, Throwable thrown) {
+ if (isLoggable(Level.FINER)) {
+ LogRecord lr = new LogRecord(Level.FINER, "THROW");
+ lr.setSourceClassName(sourceClass);
+ lr.setSourceMethodName(sourceMethod);
+ lr.setThrown(thrown);
+ doLog(lr);
+ }
+ }
+
+ @Override
+ public void severe(String msg) {
+ if (isLoggable(Level.SEVERE)) {
+ LogRecord lr = new LogRecord(Level.SEVERE, msg);
+ doLog(lr);
+ }
+ }
+
+ @Override
+ public void warning(String msg) {
+ if (isLoggable(Level.WARNING)) {
+ LogRecord lr = new LogRecord(Level.WARNING, msg);
+ doLog(lr);
+ }
+ }
+
+ @Override
+ public void info(String msg) {
+ if (isLoggable(Level.INFO)) {
+ LogRecord lr = new LogRecord(Level.INFO, msg);
+ doLog(lr);
+ }
+ }
+
+ @Override
+ public void config(String msg) {
+ if (isLoggable(Level.CONFIG)) {
+ LogRecord lr = new LogRecord(Level.CONFIG, msg);
+ doLog(lr);
+ }
+ }
+
+ @Override
+ public void fine(String msg) {
+ if (isLoggable(Level.FINE)) {
+ LogRecord lr = new LogRecord(Level.FINE, msg);
+ doLog(lr);
+ }
+ }
+
+ @Override
+ public void finer(String msg) {
+ if (isLoggable(Level.FINER)) {
+ LogRecord lr = new LogRecord(Level.FINER, msg);
+ doLog(lr);
+ }
+ }
+
+ @Override
+ public void finest(String msg) {
+ if (isLoggable(Level.FINEST)) {
+ LogRecord lr = new LogRecord(Level.FINEST, msg);
+ doLog(lr);
+ }
+ }
+
+ @Override
+ public void setLevel(Level newLevel) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public abstract Level getLevel();
+
+ @Override
+ public boolean isLoggable(Level level) {
+ Level l = getLevel();
+ return level.intValue() >= l.intValue() && l != Level.OFF;
+ }
+
+ protected boolean supportsHandlers() {
+ return false;
+ }
+
+ @Override
+ public synchronized void addHandler(Handler handler) {
+ if (supportsHandlers()) {
+ super.addHandler(handler);
+ return;
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public synchronized void removeHandler(Handler handler) {
+ if (supportsHandlers()) {
+ super.removeHandler(handler);
+ return;
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public synchronized Handler[] getHandlers() {
+ if (supportsHandlers()) {
+ return super.getHandlers();
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public synchronized void setUseParentHandlers(boolean useParentHandlers) {
+ if (supportsHandlers()) {
+ super.setUseParentHandlers(useParentHandlers);
+ return;
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public synchronized boolean getUseParentHandlers() {
+ if (supportsHandlers()) {
+ return super.getUseParentHandlers();
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Logger getParent() {
+ return null;
+ }
+
+ @Override
+ public void setParent(Logger parent) {
+ throw new UnsupportedOperationException();
+ }
+
+ protected void doLog(LogRecord lr) {
+ lr.setLoggerName(getName());
+ String rbname = getResourceBundleName();
+ if (rbname != null) {
+ lr.setResourceBundleName(rbname);
+ lr.setResourceBundle(getResourceBundle());
+ }
+ internalLog(lr);
+ }
+
+ protected void doLog(LogRecord lr, String rbname) {
+ lr.setLoggerName(getName());
+ if (rbname != null) {
+ lr.setResourceBundleName(rbname);
+ lr.setResourceBundle(loadResourceBundle(rbname));
+ }
+ internalLog(lr);
+ }
+
+ protected void internalLog(LogRecord record) {
+ Filter filter = getFilter();
+ if (filter != null && !filter.isLoggable(record)) {
+ return;
+ }
+ String msg = formatMessage(record);
+ internalLogFormatted(msg, record);
+ }
+
+ protected abstract void internalLogFormatted(String msg, LogRecord record);
+
+ protected String formatMessage(LogRecord record) {
+ String format = record.getMessage();
+ ResourceBundle catalog = record.getResourceBundle();
+ if (catalog != null) {
+ try {
+ format = catalog.getString(record.getMessage());
+ } catch (MissingResourceException ex) {
+ format = record.getMessage();
+ }
+ }
+ try {
+ Object[] parameters = record.getParameters();
+ if (parameters == null || parameters.length == 0) {
+ return format;
+ }
+ if (format.indexOf("{0") >= 0 || format.indexOf("{1") >= 0
+ || format.indexOf("{2") >= 0 || format.indexOf("{3") >= 0) {
+ return java.text.MessageFormat.format(format, parameters);
+ }
+ return format;
+ } catch (Exception ex) {
+ return format;
+ }
+ }
+
+ /**
+ * Load the specified resource bundle
+ *
+ * @param resourceBundleName
+ * the name of the resource bundle to load, cannot be null
+ * @return the loaded resource bundle.
+ * @throws java.util.MissingResourceException
+ * If the specified resource bundle can not be loaded.
+ */
+ static ResourceBundle loadResourceBundle(String resourceBundleName) {
+ // try context class loader to load the resource
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ if (null != cl) {
+ try {
+ return ResourceBundle.getBundle(resourceBundleName, Locale.getDefault(), cl);
+ } catch (MissingResourceException e) {
+ // Failed to load using context classloader, ignore
+ }
+ }
+ // try system class loader to load the resource
+ cl = ClassLoader.getSystemClassLoader();
+ if (null != cl) {
+ try {
+ return ResourceBundle.getBundle(resourceBundleName, Locale.getDefault(), cl);
+ } catch (MissingResourceException e) {
+ // Failed to load using system classloader, ignore
+ }
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/transform/src/patch/java/org/apache/cxf/common/logging/LogUtils.java b/transform/src/patch/java/org/apache/cxf/common/logging/LogUtils.java
new file mode 100644
index 0000000..09727e6
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/logging/LogUtils.java
@@ -0,0 +1,485 @@
+/**
+ * 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.common.logging;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+import org.apache.cxf.common.i18n.BundleUtils;
+import org.apache.cxf.common.util.StringUtils;
+
+
+/**
+ * A container for static utility methods related to logging.
+ * By default, CXF logs to java.util.logging. An application can change this. To log to another system, the
+ * application must provide an object that extends {@link AbstractDelegatingLogger}, and advertise that class
+ * via one of the following mechanisms:
+ * <ul>
+ * <li>Create a file, in the classpath, named META-INF/cxf/org.apache.cxf.Logger.
+ * This file should contain the fully-qualified name
+ * of the class, with no comments, on a single line.</li>
+ * <li>Call {@link #setLoggerClass(Class)} with a Class<?> reference to the logger class.</li>
+ * </ul>
+ * CXF provides {@link Slf4jLogger} to use slf4j instead of java.util.logging.
+ */
+public final class LogUtils {
+ private static final String KEY = "org.apache.cxf.Logger";
+
+ private static final Object[] NO_PARAMETERS = new Object[0];
+
+
+ private static Class<?> loggerClass;
+
+ /**
+ * Prevents instantiation.
+ */
+ private LogUtils() {
+ }
+
+ static {
+ JDKBugHacks.doHacks();
+
+ try {
+
+ String cname = null;
+ try {
+ cname = AccessController.doPrivileged(new PrivilegedAction<String>() {
+ public String run() {
+ return System.getProperty(KEY);
+ }
+ });
+ } catch (Throwable t) {
+ //ignore - likely security exception or similar that won't allow
+ //access to the system properties. We'll continue with other methods
+ }
+ if (StringUtils.isEmpty(cname)) {
+ InputStream ins = Thread.currentThread().getContextClassLoader()
+ .getResourceAsStream("META-INF/cxf/" + KEY);
+ if (ins == null) {
+ ins = ClassLoader.getSystemResourceAsStream("META-INF/cxf/" + KEY);
+ }
+ if (ins != null) {
+ try (BufferedReader din = new BufferedReader(new InputStreamReader(ins))) {
+ cname = din.readLine();
+ }
+ }
+ }
+ if (StringUtils.isEmpty(cname)) {
+ try {
+ // This Class.forName likely will barf in OSGi, but it's OK
+ // as we'll just use j.u.l and pax-logging will pick it up fine
+ // If we don't call this and there isn't a slf4j impl avail,
+ // you get warnings printed to stderr about NOPLoggers and such
+ Class.forName("org.slf4j.impl.StaticLoggerBinder");
+ Class<?> cls = Class.forName("org.slf4j.LoggerFactory");
+ Class<?> fcls = cls.getMethod("getILoggerFactory").invoke(null).getClass();
+ String clsName = fcls.getName();
+ if (clsName.contains("NOPLogger")) {
+ //no real slf4j implementation, use j.u.l
+ cname = null;
+ } else if (clsName.contains("JDK14")
+ || clsName.contains("pax.logging")) {
+ //both of these we can use the appropriate j.u.l API's
+ //directly and have it work properly
+ cname = null;
+ } else {
+ // Either we cannot really detect where it's logging
+ // or we don't want to use a custom logger, so we'll
+ // go ahead and use the Slf4jLogger directly
+ cname = "org.apache.cxf.common.logging.Slf4jLogger";
+ }
+ } catch (Throwable t) {
+ //ignore - Slf4j not available
+ }
+ }
+ if (!StringUtils.isEmpty(cname)) {
+ try {
+ loggerClass = Class.forName(cname.trim(), true,
+ Thread.currentThread().getContextClassLoader());
+ } catch (Throwable ex) {
+ loggerClass = Class.forName(cname.trim());
+ }
+ getLogger(LogUtils.class).fine("Using " + loggerClass.getName() + " for logging.");
+ }
+ } catch (Throwable ex) {
+ //ignore - if we get here, some issue prevented the logger class from being loaded.
+ //maybe a ClassNotFound or NoClassDefFound or similar. Just use j.u.l
+ loggerClass = null;
+ }
+ }
+
+
+ /**
+ * Specify a logger class that inherits from {@link AbstractDelegatingLogger}.
+ * Enable users to use their own logger implementation.
+ */
+ public static void setLoggerClass(Class<? extends AbstractDelegatingLogger> cls) {
+ loggerClass = cls;
+ }
+
+
+ /**
+ * Get a Logger with the associated default resource bundle for the class.
+ *
+ * @param cls the Class to contain the Logger
+ * @return an appropriate Logger
+ */
+ public static Logger getLogger(Class<?> cls) {
+ return createLogger(cls, null, cls.getName());
+ }
+
+ /**
+ * Get a Logger with an associated resource bundle.
+ *
+ * @param cls the Class to contain the Logger
+ * @param resourcename the resource name
+ * @return an appropriate Logger
+ */
+ public static Logger getLogger(Class<?> cls, String resourcename) {
+ return createLogger(cls, resourcename, cls.getName());
+ }
+
+ /**
+ * Get a Logger with an associated resource bundle.
+ *
+ * @param cls the Class to contain the Logger (to find resources)
+ * @param resourcename the resource name
+ * @param loggerName the full name for the logger
+ * @return an appropriate Logger
+ */
+ public static Logger getLogger(Class<?> cls,
+ String resourcename,
+ String loggerName) {
+ return createLogger(cls, resourcename, loggerName);
+ }
+
+ /**
+ * Get a Logger with the associated default resource bundle for the class.
+ *
+ * @param cls the Class to contain the Logger
+ * @return an appropriate Logger
+ */
+ public static Logger getL7dLogger(Class<?> cls) {
+ return createLogger(cls, null, cls.getName());
+ }
+
+ /**
+ * Get a Logger with an associated resource bundle.
+ *
+ * @param cls the Class to contain the Logger
+ * @param resourcename the resource name
+ * @return an appropriate Logger
+ */
+ public static Logger getL7dLogger(Class<?> cls, String resourcename) {
+ return createLogger(cls, resourcename, cls.getName());
+ }
+
+ /**
+ * Get a Logger with an associated resource bundle.
+ *
+ * @param cls the Class to contain the Logger (to find resources)
+ * @param resourcename the resource name
+ * @param loggerName the full name for the logger
+ * @return an appropriate Logger
+ */
+ public static Logger getL7dLogger(Class<?> cls,
+ String resourcename,
+ String loggerName) {
+ return createLogger(cls, resourcename, loggerName);
+ }
+
+ /**
+ * Create a logger
+ */
+ protected static Logger createLogger(Class<?> cls,
+ String name,
+ String loggerName) {
+ ClassLoader orig = getContextClassLoader();
+ ClassLoader n = getClassLoader(cls);
+ if (n != null) {
+ setContextClassLoader(n);
+ }
+ String bundleName = name;
+ try {
+ ResourceBundle b = null;
+ if (bundleName == null) {
+ //grab the bundle prior to the call to Logger.getLogger(...) so the
+ //ResourceBundle can be loaded outside the big sync block that getLogger really is
+ bundleName = BundleUtils.getBundleName(cls);
+ try {
+ b = BundleUtils.getBundle(cls);
+ } catch (MissingResourceException rex) {
+ //ignore
+ }
+ } else {
+ bundleName = BundleUtils.getBundleName(cls, bundleName);
+ try {
+ b = BundleUtils.getBundle(cls, bundleName);
+ } catch (MissingResourceException rex) {
+ //ignore
+ }
+ }
+ if (b != null) {
+ b.getLocale();
+ }
+
+ if (loggerClass != null) {
+ try {
+ Constructor<?> cns = loggerClass.getConstructor(String.class, String.class);
+ if (name == null) {
+ try {
+ return (Logger) cns.newInstance(loggerName, bundleName);
+ } catch (InvocationTargetException ite) {
+ if (ite.getTargetException() instanceof MissingResourceException) {
+ return (Logger) cns.newInstance(loggerName, null);
+ }
+ throw ite;
+ }
+ }
+ try {
+ return (Logger) cns.newInstance(loggerName, bundleName);
+ } catch (InvocationTargetException ite) {
+ if (ite.getTargetException() instanceof MissingResourceException) {
+ throw (MissingResourceException)ite.getTargetException();
+ }
+ throw ite;
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ Logger logger;
+ try {
+ logger = Logger.getLogger(loggerName, bundleName); //NOPMD
+ } catch (IllegalArgumentException | MissingResourceException ex) {
+ //likely a mismatch on the bundle name, just return the default
+ logger = Logger.getLogger(loggerName); //NOPMD
+ }
+
+ return logger;
+ } finally {
+ if (n != orig) {
+ setContextClassLoader(orig);
+ }
+ }
+ }
+
+ private static void setContextClassLoader(final ClassLoader classLoader) {
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ public Object run() {
+ Thread.currentThread().setContextClassLoader(classLoader);
+ return null;
+ }
+ });
+ } else {
+ Thread.currentThread().setContextClassLoader(classLoader);
+ }
+ }
+
+ private static ClassLoader getContextClassLoader() {
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+ public ClassLoader run() {
+ return Thread.currentThread().getContextClassLoader();
+ }
+ });
+ }
+ return Thread.currentThread().getContextClassLoader();
+ }
+
+ private static ClassLoader getClassLoader(final Class<?> clazz) {
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+ public ClassLoader run() {
+ return clazz.getClassLoader();
+ }
+ });
+ }
+ return clazz.getClassLoader();
+ }
+
+ /**
+ * Allows both parameter substitution and a typed Throwable to be logged.
+ *
+ * @param logger the Logger the log to
+ * @param level the severity level
+ * @param message the log message
+ * @param throwable the Throwable to log
+ * @param parameter the parameter to substitute into message
+ */
+ public static void log(Logger logger,
+ Level level,
+ String message,
+ Throwable throwable,
+ Object parameter) {
+ if (logger.isLoggable(level)) {
+ final String formattedMessage =
+ MessageFormat.format(localize(logger, message), parameter);
+ doLog(logger, level, formattedMessage, throwable);
+ }
+ }
+
+ /**
+ * Allows both parameter substitution and a typed Throwable to be logged.
+ *
+ * @param logger the Logger the log to
+ * @param level the severity level
+ * @param message the log message
+ * @param throwable the Throwable to log
+ * @param parameters the parameters to substitute into message
+ */
+ public static void log(Logger logger,
+ Level level,
+ String message,
+ Throwable throwable,
+ Object... parameters) {
+ if (logger.isLoggable(level)) {
+ final String formattedMessage =
+ MessageFormat.format(localize(logger, message), parameters);
+ doLog(logger, level, formattedMessage, throwable);
+ }
+ }
+
+ /**
+ * Checks log level and logs
+ *
+ * @param logger the Logger the log to
+ * @param level the severity level
+ * @param message the log message
+ */
+ public static void log(Logger logger,
+ Level level,
+ String message) {
+ log(logger, level, message, NO_PARAMETERS);
+ }
+
+ /**
+ * Checks log level and logs
+ *
+ * @param logger the Logger the log to
+ * @param level the severity level
+ * @param message the log message
+ * @param throwable the Throwable to log
+ */
+ public static void log(Logger logger,
+ Level level,
+ String message,
+ Throwable throwable) {
+ log(logger, level, message, throwable, NO_PARAMETERS);
+ }
+
+ /**
+ * Checks log level and logs
+ *
+ * @param logger the Logger the log to
+ * @param level the severity level
+ * @param message the log message
+ * @param parameter the parameter to substitute into message
+ */
+ public static void log(Logger logger,
+ Level level,
+ String message,
+ Object parameter) {
+ log(logger, level, message, new Object[] {parameter});
+ }
+
+ /**
+ * Checks log level and logs
+ *
+ * @param logger the Logger the log to
+ * @param level the severity level
+ * @param message the log message
+ * @param parameters the parameters to substitute into message
+ */
+ public static void log(Logger logger,
+ Level level,
+ String message,
+ Object[] parameters) {
+ if (logger.isLoggable(level)) {
+ String msg = localize(logger, message);
+ try {
+ msg = MessageFormat.format(msg, parameters);
+ } catch (IllegalArgumentException ex) {
+ //ignore, log as is
+ }
+ doLog(logger, level, msg, null);
+ }
+ }
+
+ private static void doLog(Logger log, Level level, String msg, Throwable t) {
+ LogRecord record = new LogRecord(level, msg);
+
+ record.setLoggerName(log.getName());
+ record.setResourceBundleName(log.getResourceBundleName());
+ record.setResourceBundle(log.getResourceBundle());
+
+ if (t != null) {
+ record.setThrown(t);
+ }
+
+ //try to get the right class name/method name - just trace
+ //back the stack till we get out of this class
+ StackTraceElement[] stack = (new Throwable()).getStackTrace();
+ String cname = LogUtils.class.getName();
+ for (int x = 0; x < stack.length; x++) {
+ StackTraceElement frame = stack[x];
+ if (!frame.getClassName().equals(cname)) {
+ record.setSourceClassName(frame.getClassName());
+ record.setSourceMethodName(frame.getMethodName());
+ break;
+ }
+ }
+ log.log(record);
+ }
+
+ /**
+ * Retrieve localized message retrieved from a logger's resource
+ * bundle.
+ *
+ * @param logger the Logger
+ * @param message the message to be localized
+ */
+ private static String localize(Logger logger, String message) {
+ ResourceBundle bundle = logger.getResourceBundle();
+ try {
+ return bundle != null ? bundle.getString(message) : message;
+ } catch (MissingResourceException ex) {
+ //string not in the bundle
+ return message;
+ }
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/logging/RegexLoggingFilter.java b/transform/src/patch/java/org/apache/cxf/common/logging/RegexLoggingFilter.java
new file mode 100644
index 0000000..83178b9
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/logging/RegexLoggingFilter.java
@@ -0,0 +1,117 @@
+/**
+ * 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.common.logging;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class RegexLoggingFilter {
+
+ public static final String DEFAULT_REPLACEMENT = "*****";
+
+ private static class ReplaceRegEx {
+ private final Pattern pattern;
+ private final int group;
+ private final String replacement;
+
+ ReplaceRegEx(String pattern, int group, String replacement) {
+ this.pattern = Pattern.compile(pattern);
+ this.group = group;
+ this.replacement = replacement;
+ }
+
+ public CharSequence filter(CharSequence command) {
+ Matcher m = pattern.matcher(command);
+ int offset = 0;
+ while (m.find()) {
+ int origLen = command.length();
+ command = new StringBuilder(command)
+ .replace(m.start(group) + offset, m.end(group) + offset, replacement).toString();
+ offset += command.length() - origLen;
+ }
+ return command;
+ }
+ }
+
+ private String regPattern;
+ private int regGroup = 1;
+ private String regReplacement = DEFAULT_REPLACEMENT;
+
+ private List<ReplaceRegEx> regexs = new ArrayList<>();
+
+ public CharSequence filter(CharSequence command) {
+ if (regPattern != null) {
+ command = new ReplaceRegEx(regPattern, regGroup, regReplacement).filter(command);
+ }
+ for (ReplaceRegEx regex : regexs) {
+ command = regex.filter(command);
+ }
+ return command;
+ }
+
+ public void addRegEx(String pattern) {
+ addRegEx(pattern, 1);
+ }
+
+ public void addRegEx(String pattern, int group) {
+ addRegEx(pattern, group, DEFAULT_REPLACEMENT);
+ }
+
+ public void addRegEx(String pattern, int group, String replacement) {
+ regexs.add(new ReplaceRegEx(pattern, group, replacement));
+ }
+
+ public void addCommandOption(String option, String... commands) {
+ StringBuilder pattern = new StringBuilder("(");
+ for (String command : commands) {
+ if (pattern.length() > 1) {
+ pattern.append('|');
+ }
+ pattern.append(Pattern.quote(command));
+ }
+ pattern.append(") +.*?").append(Pattern.quote(option)).append(" +([^ ]+)");
+ regexs.add(new ReplaceRegEx(pattern.toString(), 2, DEFAULT_REPLACEMENT));
+ }
+
+ public String getPattern() {
+ return regPattern;
+ }
+
+ public void setPattern(String pattern) {
+ this.regPattern = pattern;
+ }
+
+ public String getReplacement() {
+ return regReplacement;
+ }
+
+ public void setReplacement(String replacement) {
+ this.regReplacement = replacement;
+ }
+
+ public int getGroup() {
+ return regGroup;
+ }
+
+ public void setGroup(int group) {
+ this.regGroup = group;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/spi/ClassGeneratorClassLoader.java b/transform/src/patch/java/org/apache/cxf/common/spi/ClassGeneratorClassLoader.java
new file mode 100644
index 0000000..5906c7a
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/spi/ClassGeneratorClassLoader.java
@@ -0,0 +1,153 @@
+/**
+ * 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.common.spi;
+
+import java.lang.ref.WeakReference;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.common.util.WeakIdentityHashMap;
+
+/** Class loader used to store and retrieve class generated during runtime to avoid class generation each time.
+ * inherited class use asmHelper to generate bytes and use @see #loadClass(String, Class<?>, byte[])
+ * or @see #loadClass(String, ClassLoader, byte[]) to store generated class.Class can be generated during buildtime.
+ * equivalent class is @see org.apache.cxf.common.spi.GeneratedClassClassLoader
+ * @author olivier dufour
+ */
+public class ClassGeneratorClassLoader {
+ protected static final Map<Class<?>, WeakReference<TypeHelperClassLoader>> CLASS_MAP
+ = new WeakIdentityHashMap<>();
+ protected static final Map<ClassLoader, WeakReference<TypeHelperClassLoader>> LOADER_MAP
+ = new WeakIdentityHashMap<>();
+ protected final Bus bus;
+
+ public ClassGeneratorClassLoader(final Bus bus) {
+ this.bus = bus == null ? BusFactory.getDefaultBus() : bus;
+ }
+
+ protected Class<?> loadClass(String className, Class<?> cls, byte[] bytes) {
+ GeneratedClassClassLoaderCapture capture = bus.getExtension(GeneratedClassClassLoaderCapture.class);
+ if (capture != null) {
+ capture.capture(className, bytes);
+ }
+ TypeHelperClassLoader loader = getOrCreateLoader(cls);
+ synchronized (loader) {
+ Class<?> clz = loader.lookupDefinedClass(className);
+ if (clz == null) {
+ return loader.defineClass(className, bytes);
+ }
+ return clz;
+ }
+ }
+ protected Class<?> loadClass(String className, ClassLoader l, byte[] bytes) {
+ GeneratedClassClassLoaderCapture capture = bus.getExtension(GeneratedClassClassLoaderCapture.class);
+ if (capture != null) {
+ capture.capture(className, bytes);
+ }
+ TypeHelperClassLoader loader = getOrCreateLoader(l);
+ synchronized (loader) {
+ Class<?> clz = loader.lookupDefinedClass(className);
+ if (clz == null) {
+ return loader.defineClass(className, bytes);
+ }
+ return clz;
+ }
+ }
+ protected Class<?> findClass(String className, Class<?> cls) {
+ return getOrCreateLoader(cls).lookupDefinedClass(className);
+ }
+
+ protected Class<?> findClass(String className, ClassLoader classLoader) {
+ return getOrCreateLoader(classLoader).lookupDefinedClass(className);
+ }
+
+ private static synchronized TypeHelperClassLoader getOrCreateLoader(Class<?> cls) {
+ WeakReference<TypeHelperClassLoader> ref = CLASS_MAP.get(cls);
+ TypeHelperClassLoader ret;
+ if (ref == null || ref.get() == null) {
+ ret = new TypeHelperClassLoader(cls.getClassLoader());
+ CLASS_MAP.put(cls, new WeakReference<>(ret));
+ } else {
+ ret = ref.get();
+ }
+ return ret;
+ }
+
+ private static synchronized TypeHelperClassLoader getOrCreateLoader(ClassLoader l) {
+ WeakReference<TypeHelperClassLoader> ref = LOADER_MAP.get(l);
+ TypeHelperClassLoader ret;
+ if (ref == null || ref.get() == null) {
+ ret = new TypeHelperClassLoader(l);
+ LOADER_MAP.put(l, new WeakReference<>(ret));
+ } else {
+ ret = ref.get();
+ }
+ return ret;
+ }
+
+ public static class TypeHelperClassLoader extends ClassLoader {
+ private final ConcurrentHashMap<String, Class<?>> defined = new ConcurrentHashMap<>();
+
+ TypeHelperClassLoader(ClassLoader parent) {
+ super(parent);
+ }
+
+ public Class<?> lookupDefinedClass(String name) {
+ return defined.get(StringUtils.slashesToPeriod(name));
+ }
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ if (name.endsWith("package-info")) {
+ return getParent().loadClass(name);
+ }
+ return super.findClass(name);
+ }
+
+ public Class<?> defineClass(String name, byte[] bytes) {
+ Class<?> ret = defined.get(StringUtils.slashesToPeriod(name));
+ if (ret != null) {
+ return ret;
+ }
+ if (name.endsWith("package-info")) {
+ String s = name.substring(0, name.length() - 13);
+ Package p = super.getPackage(s);
+ if (p == null) {
+ definePackage(StringUtils.slashesToPeriod(s),
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null);
+ }
+ }
+
+ ret = defined.computeIfAbsent(StringUtils.slashesToPeriod(name),
+ key -> TypeHelperClassLoader.super.defineClass(key, bytes, 0, bytes.length));
+
+ return ret;
+ }
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/spi/NamespaceClassGenerator.java b/transform/src/patch/java/org/apache/cxf/common/spi/NamespaceClassGenerator.java
new file mode 100644
index 0000000..48c755b
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/spi/NamespaceClassGenerator.java
@@ -0,0 +1,450 @@
+/**
+ * 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.common.spi;
+
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.ASMHelper;
+import org.apache.cxf.common.util.OpcodesProxy;
+
+public class NamespaceClassGenerator extends ClassGeneratorClassLoader implements NamespaceClassCreator {
+
+ private static final Logger LOG = LogUtils.getL7dLogger(ClassGeneratorClassLoader.class);
+ private final ASMHelper helper;
+
+ public NamespaceClassGenerator(Bus bus) {
+ super(bus);
+ helper = bus.getExtension(ASMHelper.class);
+ }
+
+ @Override
+ public synchronized Class<?> createNamespaceWrapperClass(Class<?> mcls, Map<String, String> map) {
+ String postFix = "";
+
+ if (mcls.getName().contains("eclipse")) {
+ return createEclipseNamespaceMapper();
+ } else if (mcls.getName().contains(".internal")) {
+ postFix = "Internal";
+ } else if (mcls.getName().contains("com.sun")) {
+ postFix = "RI";
+ }
+
+ String className = "org.apache.cxf.jaxb.NamespaceMapper";
+ className += postFix;
+ Class<?> cls = findClass(className, NamespaceClassCreator.class);
+ Throwable t = null;
+ if (cls == null) {
+ try {
+ byte[] bts = createNamespaceWrapperInternal(postFix);
+ className = "org.apache.cxf.jaxb.NamespaceMapper" + postFix;
+ return loadClass(className, NamespaceClassCreator.class, bts);
+ } catch (RuntimeException ex) {
+ // continue
+ t = ex;
+ }
+ }
+ if (cls == null
+ && (!mcls.getName().contains(".internal.") && mcls.getName().contains("com.sun"))) {
+ try {
+ cls = ClassLoaderUtils.loadClass("org.apache.cxf.common.jaxb.NamespaceMapper",
+ NamespaceClassCreator.class);
+ } catch (Throwable ex2) {
+ // ignore
+ t = ex2;
+ }
+ }
+ LOG.log(Level.INFO, "Could not create a NamespaceMapper compatible with Marshaller class " + mcls.getName(), t);
+ return cls;
+ }
+
+ private Class<?> createEclipseNamespaceMapper() {
+ String className = "org.apache.cxf.jaxb.EclipseNamespaceMapper";
+ Class<?> cls = findClass(className, NamespaceClassCreator.class);
+ if (cls != null) {
+ return cls;
+ }
+ byte[] bts = doCreateEclipseNamespaceMapper();
+ //previous code use mcls instead of NamespaceClassGenerator.class
+ return loadClass(className, NamespaceClassCreator.class, bts);
+ }
+
+ /*
+ // This is the "prototype" for the ASM generated class below
+ public static class MapNamespacePrefixMapper2
+ extends org.eclipse.persistence.internal.oxm.record.namespaces.MapNamespacePrefixMapper {
+
+ String[] nsctxt;
+
+ public MapNamespacePrefixMapper2(Map<String, String> foo) {
+ super(foo);
+ }
+ public String[] getPreDeclaredNamespaceUris() {
+ String[] sup = super.getPreDeclaredNamespaceUris();
+ if (nsctxt == null) {
+ return sup;
+ }
+ List<String> s = new ArrayList<>(Arrays.asList(sup));
+ for (int x = 1; x < nsctxt.length; x = x + 2) {
+ s.remove(nsctxt[x]);
+ }
+ return s.toArray(new String[s.size()]);
+ }
+ public void setContextualNamespaceDecls(String[] f) {
+ nsctxt = f;
+ }
+ public String[] getContextualNamespaceDecls() {
+ return nsctxt;
+ }
+ }
+ */
+ //CHECKSTYLE:OFF
+ //bunch of really long ASM based methods that cannot be shortened easily
+ private byte[] doCreateEclipseNamespaceMapper() {
+ OpcodesProxy Opcodes = helper.getOpCodes();
+ String slashedName = "org/apache/cxf/jaxb/EclipseNamespaceMapper";
+ ASMHelper.ClassWriter cw = helper.createClassWriter();
+ if (cw == null) {
+ return null;
+ }
+ String superName = "org/eclipse/persistence/internal/oxm/record/namespaces/MapNamespacePrefixMapper";
+ ASMHelper.FieldVisitor fv;
+ ASMHelper.MethodVisitor mv;
+ cw.visit(Opcodes.V1_6,
+ Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER,
+ slashedName, null,
+ superName, null);
+
+ cw.visitSource("EclipseNamespaceMapper.java", null);
+
+ fv = cw.visitField(Opcodes.ACC_PRIVATE, "nsctxt", "[Ljava/lang/String;", null, null);
+ fv.visitEnd();
+
+
+ mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "(Ljava/util/Map;)V",
+ "(Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;)V", null);
+ mv.visitCode();
+ ASMHelper.Label l0 = helper.createLabel();
+ mv.visitLabel(l0);
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL,
+ superName, "<init>", "(Ljava/util/Map;)V", false);
+ ASMHelper.Label l1 = helper.createLabel();
+ mv.visitLabel(l1);
+ mv.visitInsn(Opcodes.RETURN);
+ ASMHelper.Label l2 = helper.createLabel();
+ mv.visitLabel(l2);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+
+
+ mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "setContextualNamespaceDecls", "([Ljava/lang/String;)V",
+ null, null);
+ mv.visitCode();
+ l0 = helper.createLabel();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(47, l0);
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.visitFieldInsn(Opcodes.PUTFIELD, slashedName, "nsctxt", "[Ljava/lang/String;");
+ l1 = helper.createLabel();
+ mv.visitLabel(l1);
+ mv.visitLineNumber(48, l1);
+ mv.visitInsn(Opcodes.RETURN);
+ l2 = helper.createLabel();
+ mv.visitLabel(l2);
+ mv.visitLocalVariable("this", "L" + slashedName + ";", null, l0, l2, 0);
+ mv.visitLocalVariable("contextualNamespaceDecls", "[Ljava/lang/String;", null, l0, l2, 1);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+
+ mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "getContextualNamespaceDecls", "()[Ljava/lang/String;", null, null);
+ mv.visitCode();
+ l0 = helper.createLabel();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(51, l0);
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitFieldInsn(Opcodes.GETFIELD, slashedName, "nsctxt", "[Ljava/lang/String;");
+ mv.visitInsn(Opcodes.ARETURN);
+ l1 = helper.createLabel();
+
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "L" + slashedName + ";", null, l0, l1, 0);
+
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+
+
+ mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "getPreDeclaredNamespaceUris", "()[Ljava/lang/String;", null, null);
+ mv.visitCode();
+ l0 = helper.createLabel();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(1036, l0);
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL,
+ superName,
+ "getPreDeclaredNamespaceUris", "()[Ljava/lang/String;", false);
+ mv.visitVarInsn(Opcodes.ASTORE, 1);
+ l1 = helper.createLabel();
+ mv.visitLabel(l1);
+ mv.visitLineNumber(1037, l1);
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitFieldInsn(Opcodes.GETFIELD, slashedName, "nsctxt", "[Ljava/lang/String;");
+ l2 = helper.createLabel();
+ mv.visitJumpInsn(Opcodes.IFNONNULL, l2);
+ ASMHelper.Label l3 = helper.createLabel();
+ mv.visitLabel(l3);
+ mv.visitLineNumber(1038, l3);
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.visitInsn(Opcodes.ARETURN);
+ mv.visitLabel(l2);
+ mv.visitLineNumber(1040, l2);
+ mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] {"[Ljava/lang/String;"}, 0, null);
+ mv.visitTypeInsn(Opcodes.NEW, "java/util/ArrayList");
+ mv.visitInsn(Opcodes.DUP);
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "asList",
+ "([Ljava/lang/Object;)Ljava/util/List;", false);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/util/ArrayList", "<init>",
+ "(Ljava/util/Collection;)V", false);
+ mv.visitVarInsn(Opcodes.ASTORE, 2);
+ ASMHelper.Label l4 = helper.createLabel();
+ mv.visitLabel(l4);
+ mv.visitLineNumber(1041, l4);
+ mv.visitInsn(Opcodes.ICONST_1);
+ mv.visitVarInsn(Opcodes.ISTORE, 3);
+ ASMHelper.Label l5 = helper.createLabel();
+ mv.visitLabel(l5);
+ ASMHelper.Label l6 = helper.createLabel();
+ mv.visitJumpInsn(Opcodes.GOTO, l6);
+ ASMHelper.Label l7 = helper.createLabel();
+ mv.visitLabel(l7);
+ mv.visitLineNumber(1042, l7);
+ mv.visitFrame(Opcodes.F_APPEND, 2, new Object[] {"java/util/List", Opcodes.INTEGER}, 0, null);
+ mv.visitVarInsn(Opcodes.ALOAD, 2);
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitFieldInsn(Opcodes.GETFIELD, slashedName, "nsctxt", "[Ljava/lang/String;");
+ mv.visitVarInsn(Opcodes.ILOAD, 3);
+ mv.visitInsn(Opcodes.AALOAD);
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List", "remove", "(Ljava/lang/Object;)Z", true);
+ mv.visitInsn(Opcodes.POP);
+ ASMHelper.Label l8 = helper.createLabel();
+ mv.visitLabel(l8);
+ mv.visitLineNumber(1041, l8);
+ mv.visitIincInsn(3, 2);
+ mv.visitLabel(l6);
+ mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitVarInsn(Opcodes.ILOAD, 3);
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitFieldInsn(Opcodes.GETFIELD,
+ slashedName,
+ "nsctxt", "[Ljava/lang/String;");
+ mv.visitInsn(Opcodes.ARRAYLENGTH);
+ mv.visitJumpInsn(Opcodes.IF_ICMPLT, l7);
+ ASMHelper.Label l9 = helper.createLabel();
+ mv.visitLabel(l9);
+ mv.visitLineNumber(1044, l9);
+ mv.visitVarInsn(Opcodes.ALOAD, 2);
+ mv.visitVarInsn(Opcodes.ALOAD, 2);
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List", "size", "()I", true);
+ mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List",
+ "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", true);
+ mv.visitTypeInsn(Opcodes.CHECKCAST, "[Ljava/lang/String;");
+ mv.visitInsn(Opcodes.ARETURN);
+ ASMHelper.Label l10 = helper.createLabel();
+ mv.visitLabel(l10);
+ mv.visitLocalVariable("this", "L" + slashedName + ";",
+ null, l0, l10, 0);
+ mv.visitLocalVariable("sup", "[Ljava/lang/String;", null, l1, l10, 1);
+ mv.visitLocalVariable("s", "Ljava/util/List;", "Ljava/util/List<Ljava/lang/String;>;", l4, l10, 2);
+ mv.visitLocalVariable("x", "I", null, l5, l9, 3);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ private byte[] createNamespaceWrapperInternal(String postFix) {
+
+ String superName = "com/sun/xml/"
+ + ("RI".equals(postFix) ? "" : "internal/")
+ + "bind/marshaller/NamespacePrefixMapper";
+ String postFixedName = "org/apache/cxf/jaxb/NamespaceMapper" + postFix;
+ ASMHelper.ClassWriter cw = helper.createClassWriter();
+ if (cw == null) {
+ return null;
+ }
+ ASMHelper.FieldVisitor fv;
+ ASMHelper.MethodVisitor mv;
+ OpcodesProxy opcodes= helper.getOpCodes();
+ cw.visit(opcodes.V1_6,
+ opcodes.ACC_PUBLIC + opcodes.ACC_FINAL + opcodes.ACC_SUPER,
+ postFixedName, null,
+ superName, null);
+
+ cw.visitSource("NamespaceMapper.java", null);
+
+ fv = cw.visitField(opcodes.ACC_PRIVATE + opcodes.ACC_FINAL,
+ "nspref", "Ljava/util/Map;",
+ "Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;", null);
+ fv.visitEnd();
+
+ fv = cw.visitField(opcodes.ACC_PRIVATE, "nsctxt", "[Ljava/lang/String;", null, null);
+ fv.visitEnd();
+
+ fv = cw.visitField(opcodes.ACC_PRIVATE + opcodes.ACC_FINAL + opcodes.ACC_STATIC,
+ "EMPTY_STRING", "[Ljava/lang/String;", null, null);
+ fv.visitEnd();
+
+ mv = cw.visitMethod(opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
+ mv.visitCode();
+ ASMHelper.Label l0 = helper.createLabel();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(30, l0);
+ mv.visitInsn(opcodes.ICONST_0);
+ mv.visitTypeInsn(opcodes.ANEWARRAY, "java/lang/String");
+ mv.visitFieldInsn(opcodes.PUTSTATIC, postFixedName, "EMPTY_STRING", "[Ljava/lang/String;");
+ mv.visitInsn(opcodes.RETURN);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+
+ mv = cw.visitMethod(opcodes.ACC_PUBLIC, "<init>",
+ "(Ljava/util/Map;)V",
+ "(Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;)V", null);
+ mv.visitCode();
+ l0 = helper.createLabel();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(32, l0);
+ mv.visitVarInsn(opcodes.ALOAD, 0);
+ mv.visitMethodInsn(opcodes.INVOKESPECIAL, superName, "<init>", "()V", false);
+ ASMHelper.Label l1 = helper.createLabel();
+ mv.visitLabel(l1);
+ mv.visitLineNumber(29, l1);
+ mv.visitVarInsn(opcodes.ALOAD, 0);
+ mv.visitFieldInsn(opcodes.GETSTATIC, postFixedName, "EMPTY_STRING", "[Ljava/lang/String;");
+ mv.visitFieldInsn(opcodes.PUTFIELD, postFixedName, "nsctxt", "[Ljava/lang/String;");
+ ASMHelper.Label l2 = helper.createLabel();
+ mv.visitLabel(l2);
+ mv.visitLineNumber(33, l2);
+ mv.visitVarInsn(opcodes.ALOAD, 0);
+ mv.visitVarInsn(opcodes.ALOAD, 1);
+ mv.visitFieldInsn(opcodes.PUTFIELD, postFixedName, "nspref", "Ljava/util/Map;");
+ ASMHelper.Label l3 = helper.createLabel();
+ mv.visitLabel(l3);
+ mv.visitLineNumber(34, l3);
+ mv.visitInsn(opcodes.RETURN);
+ ASMHelper.Label l4 = helper.createLabel();
+ mv.visitLabel(l4);
+ mv.visitLocalVariable("this", "L" + postFixedName + ";", null, l0, l4, 0);
+ mv.visitLocalVariable("nspref",
+ "Ljava/util/Map;", "Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;",
+ l0, l4, 1);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+
+ mv = cw.visitMethod(opcodes.ACC_PUBLIC, "getPreferredPrefix",
+ "(Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;",
+ null, null);
+ mv.visitCode();
+ l0 = helper.createLabel();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(39, l0);
+ mv.visitVarInsn(opcodes.ALOAD, 0);
+ mv.visitFieldInsn(opcodes.GETFIELD, postFixedName, "nspref", "Ljava/util/Map;");
+ mv.visitVarInsn(opcodes.ALOAD, 1);
+ mv.visitMethodInsn(opcodes.INVOKEINTERFACE, "java/util/Map",
+ "get", "(Ljava/lang/Object;)Ljava/lang/Object;", true);
+ mv.visitTypeInsn(opcodes.CHECKCAST, "java/lang/String");
+ mv.visitVarInsn(opcodes.ASTORE, 4);
+ l1 = helper.createLabel();
+ mv.visitLabel(l1);
+ mv.visitLineNumber(40, l1);
+ mv.visitVarInsn(opcodes.ALOAD, 4);
+ l2 = helper.createLabel();
+ mv.visitJumpInsn(opcodes.IFNULL, l2);
+ l3 = helper.createLabel();
+ mv.visitLabel(l3);
+ mv.visitLineNumber(41, l3);
+ mv.visitVarInsn(opcodes.ALOAD, 4);
+ mv.visitInsn(opcodes.ARETURN);
+ mv.visitLabel(l2);
+ mv.visitLineNumber(43, l2);
+ mv.visitFrame(opcodes.F_APPEND, 1, new Object[] {"java/lang/String"}, 0, null);
+ mv.visitVarInsn(opcodes.ALOAD, 2);
+ mv.visitInsn(opcodes.ARETURN);
+ l4 = helper.createLabel();
+ mv.visitLabel(l4);
+ mv.visitLocalVariable("this", "L" + postFixedName + ";", null, l0, l4, 0);
+ mv.visitLocalVariable("namespaceUri", "Ljava/lang/String;", null, l0, l4, 1);
+ mv.visitLocalVariable("suggestion", "Ljava/lang/String;", null, l0, l4, 2);
+ mv.visitLocalVariable("requirePrefix", "Z", null, l0, l4, 3);
+ mv.visitLocalVariable("prefix", "Ljava/lang/String;", null, l1, l4, 4);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+
+ mv = cw.visitMethod(opcodes.ACC_PUBLIC, "setContextualNamespaceDecls", "([Ljava/lang/String;)V", null, null);
+ mv.visitCode();
+ l0 = helper.createLabel();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(47, l0);
+ mv.visitVarInsn(opcodes.ALOAD, 0);
+ mv.visitVarInsn(opcodes.ALOAD, 1);
+ mv.visitFieldInsn(opcodes.PUTFIELD, postFixedName, "nsctxt", "[Ljava/lang/String;");
+ l1 = helper.createLabel();
+ mv.visitLabel(l1);
+ mv.visitLineNumber(48, l1);
+ mv.visitInsn(opcodes.RETURN);
+ l2 = helper.createLabel();
+ mv.visitLabel(l2);
+ mv.visitLocalVariable("this", "L" + postFixedName + ";", null, l0, l2, 0);
+ mv.visitLocalVariable("contextualNamespaceDecls", "[Ljava/lang/String;", null, l0, l2, 1);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+
+ mv = cw.visitMethod(opcodes.ACC_PUBLIC, "getContextualNamespaceDecls", "()[Ljava/lang/String;", null, null);
+ mv.visitCode();
+ l0 = helper.createLabel();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(51, l0);
+ mv.visitVarInsn(opcodes.ALOAD, 0);
+ mv.visitFieldInsn(opcodes.GETFIELD, postFixedName, "nsctxt", "[Ljava/lang/String;");
+ mv.visitInsn(opcodes.ARETURN);
+ l1 = helper.createLabel();
+
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "L" + postFixedName + ";", null, l0, l1, 0);
+
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ //CHECKSTYLE:ON
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/util/ASMHelperImpl.java b/transform/src/patch/java/org/apache/cxf/common/util/ASMHelperImpl.java
new file mode 100644
index 0000000..cc01f14
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/util/ASMHelperImpl.java
@@ -0,0 +1,273 @@
+/**
+ * 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.common.util;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+
+
+public class ASMHelperImpl implements ASMHelper {
+ protected static final Map<Class<?>, String> PRIMITIVE_MAP = new HashMap<>();
+ protected static final Map<Class<?>, String> NONPRIMITIVE_MAP = new HashMap<>();
+ protected static final Map<Class<?>, Integer> PRIMITIVE_ZERO_MAP = new HashMap<>();
+
+ protected boolean badASM;
+ private Class<?> cwClass;
+
+ public ASMHelperImpl() {
+
+ }
+
+ static {
+ PRIMITIVE_MAP.put(Byte.TYPE, "B");
+ PRIMITIVE_MAP.put(Boolean.TYPE, "Z");
+ PRIMITIVE_MAP.put(Long.TYPE, "J");
+ PRIMITIVE_MAP.put(Integer.TYPE, "I");
+ PRIMITIVE_MAP.put(Short.TYPE, "S");
+ PRIMITIVE_MAP.put(Character.TYPE, "C");
+ PRIMITIVE_MAP.put(Float.TYPE, "F");
+ PRIMITIVE_MAP.put(Double.TYPE, "D");
+
+ NONPRIMITIVE_MAP.put(Byte.TYPE, Byte.class.getName().replaceAll("\\.", "/"));
+ NONPRIMITIVE_MAP.put(Boolean.TYPE, Boolean.class.getName().replaceAll("\\.", "/"));
+ NONPRIMITIVE_MAP.put(Long.TYPE, Long.class.getName().replaceAll("\\.", "/"));
+ NONPRIMITIVE_MAP.put(Integer.TYPE, Integer.class.getName().replaceAll("\\.", "/"));
+ NONPRIMITIVE_MAP.put(Short.TYPE, Short.class.getName().replaceAll("\\.", "/"));
+ NONPRIMITIVE_MAP.put(Character.TYPE, Character.class.getName().replaceAll("\\.", "/"));
+ NONPRIMITIVE_MAP.put(Float.TYPE, Float.class.getName().replaceAll("\\.", "/"));
+ NONPRIMITIVE_MAP.put(Double.TYPE, Double.class.getName().replaceAll("\\.", "/"));
+ }
+
+ private void tryClass(String s) {
+ if (cwClass == null) {
+ try {
+ Class<?> c2 = ClassLoaderUtils.loadClass(s, ASMHelperImpl.class);
+
+ //old versions don't have this, but we need it
+ Class<?> cls = ClassLoaderUtils.loadClass(c2.getPackage().getName() + ".MethodVisitor", c2);
+ cls.getMethod("visitFrame", Integer.TYPE, Integer.TYPE,
+ Object[].class, Integer.TYPE, Object[].class);
+ cwClass = c2;
+ } catch (Throwable t) {
+ //ignore
+ }
+ }
+ }
+ private Class<?> getASMClassWriterClass() {
+ //force this to make sure the proper OSGi import is generated
+ return org.objectweb.asm.ClassWriter.class;
+ }
+
+ public synchronized Class<?> getASMClass() throws ClassNotFoundException {
+ if (cwClass == null) {
+ //try the "real" asm first, then the others
+ tryClass("org.objectweb.asm.ClassWriter");
+ tryClass("org.apache.xbean.asm9.ClassWriter");
+ tryClass("org.apache.xbean.asm8.ClassWriter");
+ tryClass("org.apache.xbean.asm7.ClassWriter");
+ tryClass("org.apache.xbean.asm5.ClassWriter");
+ tryClass("org.apache.xbean.asm6.ClassWriter");
+ tryClass("org.apache.xbean.asm4.ClassWriter");
+ tryClass("org.apache.xbean.asm.ClassWriter");
+ tryClass("org.springframework.asm.ClassWriter");
+ if (cwClass == null) {
+ cwClass = getASMClassWriterClass();
+ }
+ }
+ return cwClass;
+ }
+ public OpcodesProxy getOpCodes() {
+ OpcodesProxy ops = new OpcodesProxy(this);
+ PRIMITIVE_ZERO_MAP.put(Byte.TYPE, ops.ICONST_0);
+ PRIMITIVE_ZERO_MAP.put(Boolean.TYPE, ops.ICONST_0);
+ PRIMITIVE_ZERO_MAP.put(Long.TYPE, ops.LCONST_0);
+ PRIMITIVE_ZERO_MAP.put(Integer.TYPE, ops.ICONST_0);
+ PRIMITIVE_ZERO_MAP.put(Short.TYPE, ops.ICONST_0);
+ PRIMITIVE_ZERO_MAP.put(Character.TYPE, ops.ICONST_0);
+ PRIMITIVE_ZERO_MAP.put(Float.TYPE, ops.FCONST_0);
+ PRIMITIVE_ZERO_MAP.put(Double.TYPE, ops.DCONST_0);
+ return ops;
+ }
+ public void setBadASM(boolean b) {
+ badASM = b;
+ }
+
+ public String getMethodSignature(Method m) {
+ StringBuilder buf = new StringBuilder("(");
+ for (Class<?> cl : m.getParameterTypes()) {
+ buf.append(getClassCode(cl));
+ }
+ buf.append(')');
+ buf.append(getClassCode(m.getReturnType()));
+
+ return buf.toString();
+ }
+
+ @Override
+ public String getNonPrimitive(Class<?> tp) {
+ return NONPRIMITIVE_MAP.get(tp);
+ }
+ @Override
+ public String getPrimitive(Class<?> tp) {
+ return PRIMITIVE_MAP.get(tp);
+ }
+
+
+
+
+ public String getClassCode(Class<?> cl) {
+ if (cl == Void.TYPE) {
+ return "V";
+ }
+ if (cl.isPrimitive()) {
+ return PRIMITIVE_MAP.get(cl);
+ }
+ if (cl.isArray()) {
+ return "[" + getClassCode(cl.getComponentType());
+ }
+ return "L" + StringUtils.periodToSlashes(cl.getName()) + ";";
+ }
+ public String getClassCode(java.lang.reflect.Type type) {
+ if (type instanceof Class) {
+ return getClassCode((Class<?>)type);
+ } else if (type instanceof GenericArrayType) {
+ GenericArrayType at = (GenericArrayType)type;
+ return "[" + getClassCode(at.getGenericComponentType());
+ } else if (type instanceof TypeVariable) {
+ TypeVariable<?> tv = (TypeVariable<?>)type;
+ java.lang.reflect.Type[] bounds = tv.getBounds();
+ if (bounds != null && bounds.length == 1) {
+ return getClassCode(bounds[0]);
+ }
+ throw new IllegalArgumentException("Unable to determine type for: " + tv);
+ } else if (type instanceof ParameterizedType) {
+ ParameterizedType pt = (ParameterizedType)type;
+ StringBuilder a = new StringBuilder(getClassCode(pt.getRawType()));
+ if (!pt.getRawType().equals(Enum.class)) {
+ a.setLength(a.length() - 1);
+ a.append('<');
+
+ for (java.lang.reflect.Type t : pt.getActualTypeArguments()) {
+ a.append(getClassCode(t));
+ }
+ a.append(">;");
+ }
+ return a.toString();
+ } else if (type instanceof WildcardType) {
+ WildcardType wt = (WildcardType)type;
+ StringBuilder a = new StringBuilder();
+ java.lang.reflect.Type[] lowBounds = wt.getLowerBounds();
+ java.lang.reflect.Type[] upBounds = wt.getUpperBounds();
+ for (java.lang.reflect.Type t : upBounds) {
+ a.append('+');
+ a.append(getClassCode(t));
+ }
+ for (java.lang.reflect.Type t : lowBounds) {
+ a.append('-');
+ a.append(getClassCode(t));
+ }
+ return a.toString();
+ }
+ return null;
+ }
+
+ public ClassWriter createClassWriter() {
+ Object newCw = null;
+ if (!badASM) {
+ if (cwClass == null) {
+ try {
+ cwClass = getASMClass();
+ } catch (Throwable error) {
+ badASM = true;
+ throw new RuntimeException("No ASM ClassWriterFound", error);
+ }
+ }
+ // ASM >= 3.x (since cxf is java 8 min we don't care of asm 1/2)
+ try {
+ Constructor<?> cons = cwClass.getConstructor(Integer.TYPE);
+ int i = cwClass.getField("COMPUTE_MAXS").getInt(null);
+ i |= cwClass.getField("COMPUTE_FRAMES").getInt(null);
+ newCw = cons.newInstance(Integer.valueOf(i));
+ } catch (Throwable e1) {
+ // ignore
+ }
+ }
+ if (newCw != null) {
+ return ReflectionInvokationHandler.createProxyWrapper(newCw, ClassWriter.class);
+ }
+ return null;
+ }
+
+
+ public ASMType getType(final String type) {
+ try {
+ final Class<?> cls = ClassLoaderUtils.loadClass(cwClass.getPackage().getName() + ".Type", cwClass);
+ final Method m = cls.getMethod("getType", String.class);
+ final Method m2 = cls.getMethod("getOpcode", Integer.TYPE);
+ @SuppressWarnings("unused")
+ ASMType t = new ASMType() {
+ Object tp = ReflectionUtil.setAccessible(m).invoke(null, type);
+ public Object getValue() {
+ return tp;
+ }
+ public Class<?> realType() {
+ return cls;
+ }
+ public int getOpcode(int ireturn) {
+ try {
+ return (Integer)ReflectionUtil.setAccessible(m2).invoke(tp, ireturn);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+ return t;
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ public Label createLabel() {
+ try {
+ final Class<?> cls = ClassLoaderUtils.loadClass(cwClass.getPackage().getName() + ".Label",
+ cwClass);
+ @SuppressWarnings("unused")
+ Label l = new Label() {
+ Object l = cls.newInstance();
+ public Object getValue() {
+ return l;
+ }
+ public Class<?> realType() {
+ return cls;
+ }
+ };
+ return l;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/util/Base64Utility.java b/transform/src/patch/java/org/apache/cxf/common/util/Base64Utility.java
new file mode 100644
index 0000000..043eba7
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/util/Base64Utility.java
@@ -0,0 +1,474 @@
+/**
+ * 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.common.util;
+
+/**
+ * Base64Utility - this static class provides useful base64
+ * encoding utilities.
+ */
+
+// Java imports
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.logging.Logger;
+
+import org.apache.cxf.common.i18n.Message;
+import org.apache.cxf.common.logging.LogUtils;
+
+
+/**
+ * This class converts to/from base64. The alternative conversions include:
+ *
+ * encode:
+ * byte[] into String
+ * byte[] into char[]
+ * byte[] into OutStream
+ * byte[] into Writer
+ * decode:
+ * char[] into byte[]
+ * String into byte[]
+ * char[] into OutStream
+ * String into OutStream
+ *
+ */
+public final class Base64Utility {
+
+ private static final Logger LOG = LogUtils.getL7dLogger(Base64Utility.class);
+
+
+ // base 64 character set
+ //
+ private static final char[] BCS = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
+ 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+ 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', '+', '/'
+ };
+
+ private static final char[] BCS_URL_SAFE = Arrays.copyOf(BCS, BCS.length);
+
+ // base 64 padding
+ private static final char PAD = '=';
+
+ // size of base 64 decode table
+ private static final int BDTSIZE = 128;
+
+ // base 64 decode table
+ private static final byte[] BDT = new byte[128];
+
+
+ private static final int PAD_SIZE0 = 1;
+ private static final int PAD_SIZE4 = 2;
+ private static final int PAD_SIZE8 = 3;
+
+ // class static initializer for building decode table
+ static {
+ for (int i = 0; i < BDTSIZE; i++) {
+ BDT[i] = Byte.MAX_VALUE;
+ }
+
+ for (int i = 0; i < BCS.length; i++) {
+ BDT[BCS[i]] = (byte)i;
+ }
+
+ BCS_URL_SAFE[62] = '-';
+ BCS_URL_SAFE[63] = '_';
+ }
+
+
+ private Base64Utility() {
+ //utility class, never constructed
+ }
+
+
+
+ /**
+ * The <code>decode_chunk</code> routine decodes a chunk of data
+ * into its native encoding.
+ *
+ * base64 encodes each 3 octets of data into 4 characters from a
+ * limited 64 character set. The 3 octets are joined to form
+ * 24 bits which are then split into 4 x 6bit values. Each 6 bit
+ * value is then used as an index into the 64 character table of
+ * base64 chars. If the total data length is not a 3 octet multiple
+ * the '=' char is used as padding for the final 4 char group,
+ * either 1 octet + '==' or 2 octets + '='.
+ *
+ * @param id The input data to be processed
+ * @param o The offset from which to begin processing
+ * @param l The length (bound) at which processing is to end
+ * @return The decoded data
+ * @exception Base64Exception Thrown is processing fails due to
+ * formatting exceptions in the encoded data
+ */
+ public static byte[] decodeChunk(char[] id,
+ int o,
+ int l)
+ throws Base64Exception {
+
+ if (id != null && id.length == 0 && l == 0) {
+ return new byte[0];
+ }
+
+ // Keep it simple - must be >= 4. Unpadded
+ // base64 data contain < 3 octets is invalid.
+ //
+ if ((l - o) < 4) {
+ return null;
+ }
+
+ char[] ib = new char[4];
+ int ibcount = 0;
+
+ // cryan. Calc the num of octets. Each 4 chars of base64 chars
+ // (representing 24 bits) encodes 3 octets.
+ //
+ int octetCount = 3 * (l / 4);
+
+ // Final 4 chars may contain 3 octets or padded to contain
+ // 1 or 2 octets.
+ //
+ if (id[l - 1] == PAD) {
+ // TT== means last 4 chars encode 8 bits (ie subtract 2)
+ // TTT= means last 4 chars encode 16 bits (ie subtract 1)
+ octetCount -= (id[l - 2] == PAD) ? 2 : 1;
+ }
+
+ byte[] ob = new byte[octetCount];
+ int obcount = 0;
+
+ for (int i = o; i < o + l && i < id.length; i++) {
+ if (id[i] == PAD
+ || id[i] < BDT.length
+ && BDT[id[i]] != Byte.MAX_VALUE) {
+
+ ib[ibcount++] = id[i];
+
+ // Decode each 4 char sequence.
+ //
+ if (ibcount == ib.length) {
+ ibcount = 0;
+ obcount += processEncodeme(ib, ob, obcount);
+ }
+ }
+ }
+
+ if (obcount != ob.length) {
+ byte []tmp = new byte[obcount];
+ System.arraycopy(ob, 0, tmp, 0, obcount);
+ ob = tmp;
+ }
+
+ return ob;
+ }
+
+ public static byte[] decode(String id) throws Base64Exception {
+ return decode(id, false);
+ }
+
+ public static byte[] decode(String id, boolean urlSafe) throws Base64Exception {
+ if (urlSafe) {
+ id = id.replace('-', '+').replace('_', '/');
+ switch (id.length() % 4) {
+ case 0:
+ break;
+ case 2:
+ id += "==";
+ break;
+ case 3:
+ id += "=";
+ break;
+ default:
+ throw new Base64Exception(new Message("BASE64_RUNTIME_EXCEPTION", LOG));
+ }
+ }
+ try {
+ char[] cd = id.toCharArray();
+ return decodeChunk(cd, 0, cd.length);
+ } catch (Exception e) {
+ LOG.warning("Invalid base64 encoded string : " + id);
+ throw new Base64Exception(new Message("BASE64_RUNTIME_EXCEPTION", LOG), e);
+ }
+ }
+
+ public static void decode(char[] id,
+ int o,
+ int l,
+ OutputStream ostream)
+ throws Base64Exception {
+
+ try {
+ ostream.write(decodeChunk(id, o, l));
+ } catch (Exception e) {
+ LOG.warning("Invalid base64 encoded string : " + new String(id));
+ throw new Base64Exception(new Message("BASE64_RUNTIME_EXCEPTION", LOG), e);
+ }
+ }
+
+ public static void decode(String id,
+ OutputStream ostream)
+ throws Base64Exception {
+
+ try {
+ char[] cd = id.toCharArray();
+ ostream.write(decodeChunk(cd, 0, cd.length));
+ } catch (IOException ioe) {
+ throw new Base64Exception(new Message("BASE64_DECODE_IOEXCEPTION", LOG), ioe);
+ } catch (Exception e) {
+ LOG.warning("Invalid base64 encoded string : " + id);
+ throw new Base64Exception(new Message("BASE64_RUNTIME_EXCEPTION", LOG), e);
+ }
+ }
+
+ // Returns base64 representation of specified byte array.
+ //
+ public static String encode(byte[] id) {
+ return encode(id, false);
+ }
+
+ public static String encode(byte[] id, boolean urlSafe) {
+ char[] cd = encodeChunk(id, 0, id.length);
+ return new String(cd, 0, cd.length);
+ }
+
+ // Returns base64 representation of specified byte array.
+ //
+ public static char[] encodeChunk(byte[] id,
+ int o,
+ int l) {
+ return encodeChunk(id, o, l, false);
+ }
+
+ public static char[] encodeChunk(byte[] id,
+ int o,
+ int l,
+ boolean urlSafe) {
+ if (id != null && id.length == 0 && l == 0) {
+ return new char[0];
+ } else if (l <= 0) {
+ return null;
+ }
+
+ char[] out;
+
+ // If not a multiple of 3 octets then a final padded 4 char
+ // slot is needed.
+ //
+ if (l % 3 == 0) {
+ out = new char[l / 3 * 4];
+ } else {
+ int finalLen = !urlSafe ? 4 : l % 3 == 1 ? 2 : 3;
+ out = new char[l / 3 * 4 + finalLen];
+ }
+
+ int rindex = o;
+ int windex = 0;
+ int rest = l;
+
+ final char[] base64Table = urlSafe ? BCS_URL_SAFE : BCS;
+ while (rest >= 3) {
+ int i = ((id[rindex] & 0xff) << 16)
+ + ((id[rindex + 1] & 0xff) << 8)
+ + (id[rindex + 2] & 0xff);
+
+ out[windex++] = base64Table[i >> 18];
+ out[windex++] = base64Table[(i >> 12) & 0x3f];
+ out[windex++] = base64Table[(i >> 6) & 0x3f];
+ out[windex++] = base64Table[i & 0x3f];
+ rindex += 3;
+ rest -= 3;
+ }
+
+ if (rest == 1) {
+ int i = id[rindex] & 0xff;
+ out[windex++] = base64Table[i >> 2];
+ out[windex++] = base64Table[(i << 4) & 0x3f];
+ if (!urlSafe) {
+ out[windex++] = PAD;
+ out[windex] = PAD;
+ }
+ } else if (rest == 2) {
+ int i = ((id[rindex] & 0xff) << 8) + (id[rindex + 1] & 0xff);
+ out[windex++] = base64Table[i >> 10];
+ out[windex++] = base64Table[(i >> 4) & 0x3f];
+ out[windex++] = base64Table[(i << 2) & 0x3f];
+ if (!urlSafe) {
+ out[windex] = PAD;
+ }
+ }
+ return out;
+ }
+
+ public static void encodeAndStream(byte[] id,
+ int o,
+ int l,
+ OutputStream os) throws IOException {
+ encodeAndStream(id, o, l, false, os);
+ }
+
+ public static void encodeAndStream(byte[] id,
+ int o,
+ int l,
+ boolean urlSafe,
+ OutputStream os) throws IOException {
+ if (l <= 0) {
+ return;
+ }
+
+ int rindex = o;
+ int rest = l;
+ final char[] base64Table = urlSafe ? BCS_URL_SAFE : BCS;
+
+ char[] chunk = new char[4];
+ while (rest >= 3) {
+ int i = ((id[rindex] & 0xff) << 16)
+ + ((id[rindex + 1] & 0xff) << 8)
+ + (id[rindex + 2] & 0xff);
+ chunk[0] = base64Table[i >> 18];
+ chunk[1] = base64Table[(i >> 12) & 0x3f];
+ chunk[2] = base64Table[(i >> 6) & 0x3f];
+ chunk[3] = base64Table[i & 0x3f];
+ writeCharArrayToStream(chunk, 4, os);
+ rindex += 3;
+ rest -= 3;
+ }
+ if (rest == 0) {
+ return;
+ }
+ if (rest == 1) {
+ int i = id[rindex] & 0xff;
+ chunk[0] = base64Table[i >> 2];
+ chunk[1] = base64Table[(i << 4) & 0x3f];
+ if (!urlSafe) {
+ chunk[2] = PAD;
+ chunk[3] = PAD;
+ }
+ } else if (rest == 2) {
+ int i = ((id[rindex] & 0xff) << 8) + (id[rindex + 1] & 0xff);
+ chunk[0] = base64Table[i >> 10];
+ chunk[1] = base64Table[(i >> 4) & 0x3f];
+ chunk[2] = base64Table[(i << 2) & 0x3f];
+ if (!urlSafe) {
+ chunk[3] = PAD;
+ }
+ }
+ int finalLenToWrite = !urlSafe ? 4 : rest == 1 ? 2 : 3;
+ writeCharArrayToStream(chunk, finalLenToWrite, os);
+ }
+
+ private static void writeCharArrayToStream(char[] chunk, int len, OutputStream os) throws IOException {
+ // may be we can just cast to byte when creating chunk[] earlier on
+ byte[] bytes = StandardCharsets.UTF_8.encode(CharBuffer.wrap(chunk, 0, len)).array();
+ os.write(bytes);
+ }
+
+ //
+ // Outputs base64 representation of the specified byte array
+ // to a byte stream.
+ //
+ public static void encodeChunk(byte[] id,
+ int o,
+ int l,
+ OutputStream ostream) throws Base64Exception {
+ try {
+ ostream.write(new String(encodeChunk(id, o, l)).getBytes());
+ } catch (IOException e) {
+ throw new Base64Exception(new Message("BASE64_ENCODE_IOEXCEPTION", LOG), e);
+ }
+ }
+
+ // Outputs base64 representation of the specified byte
+ // array to a character stream.
+ //
+ public static void encode(byte[] id,
+ int o,
+ int l,
+ Writer writer) throws Base64Exception {
+ try {
+ writer.write(encodeChunk(id, o, l));
+ } catch (IOException e) {
+ throw new Base64Exception(new Message("BASE64_ENCODE_WRITER_IOEXCEPTION", LOG), e);
+ }
+ }
+ //---- Private static methods --------------------------------------
+
+ /**
+ * The <code>process</code> routine processes an atomic base64
+ * unit of encoding (encodeme) into its native encoding. This class is
+ * used by decode routines to do the grunt work of decoding
+ * base64 encoded information
+ *
+ * @param ib Input character buffer of encoded bytes
+ * @param ob Output byte buffer of decoded bytes
+ * @param p Pointer to the encodeme of interest
+ * @return The decoded encodeme
+ * @exception Base64Exception Thrown is processing fails due to
+ * formatting exceptions in the encoded data
+ */
+ private static int processEncodeme(char[] ib,
+ byte[] ob,
+ int p)
+ throws Base64Exception {
+
+
+ int spad = PAD_SIZE8;
+ if (ib[3] == PAD) {
+ spad = PAD_SIZE4;
+ }
+ if (ib[2] == PAD) {
+ spad = PAD_SIZE0;
+ }
+
+ int b0 = BDT[ib[0]];
+ int b1 = BDT[ib[1]];
+ int b2 = BDT[ib[2]];
+ int b3 = BDT[ib[3]];
+
+ switch (spad) {
+ case PAD_SIZE0:
+ ob[p] = (byte)(b0 << 2 & 0xfc | b1 >> 4 & 0x3);
+ return PAD_SIZE0;
+ case PAD_SIZE4:
+ ob[p++] = (byte)(b0 << 2 & 0xfc | b1 >> 4 & 0x3);
+ ob[p] = (byte)(b1 << 4 & 0xf0 | b2 >> 2 & 0xf);
+ return PAD_SIZE4;
+ case PAD_SIZE8:
+ ob[p++] = (byte)(b0 << 2 & 0xfc | b1 >> 4 & 0x3);
+ ob[p++] = (byte)(b1 << 4 & 0xf0 | b2 >> 2 & 0xf);
+ ob[p] = (byte)(b2 << 6 & 0xc0 | b3 & 0x3f);
+ return PAD_SIZE8;
+ default:
+ // We should never get here
+ throw new IllegalStateException();
+ }
+ }
+
+ public static boolean isValidBase64(int ch) {
+ return ch == PAD || BDT[ch] != Byte.MAX_VALUE;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/util/CachedClass.java b/transform/src/patch/java/org/apache/cxf/common/util/CachedClass.java
new file mode 100644
index 0000000..fe2df06
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/util/CachedClass.java
@@ -0,0 +1,37 @@
+/**
+ * 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.common.util;
+
+import java.lang.ref.WeakReference;
+
+public class CachedClass {
+ private WeakReference<Class<?>> cachedClazz;
+
+ public CachedClass(Class<?> cachedClass) {
+ this.cachedClazz = new WeakReference<>(cachedClass);
+ }
+
+ public Class<?> getCachedClass() {
+ return cachedClazz == null ? null : cachedClazz.get();
+ }
+
+ public void setCachedClass(Class<?> cachedClass) {
+ this.cachedClazz = new WeakReference<>(cachedClass);
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/util/ClassHelper.java b/transform/src/patch/java/org/apache/cxf/common/util/ClassHelper.java
new file mode 100644
index 0000000..adec469
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/util/ClassHelper.java
@@ -0,0 +1,140 @@
+/**
+ * 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.common.util;
+
+
+import java.lang.reflect.Proxy;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+
+/**
+ *
+ */
+public class ClassHelper {
+
+ public static final String USE_DEFAULT_CLASS_HELPER = "org.apache.cxf.useDefaultClassHelpers";
+
+ static final ClassHelper HELPER;
+ static final ClassUnwrapper DEFAULT_UNWRAPPER;
+ static final ClassUnwrapper UNWRAPPER;
+
+ /**
+ * Default class unwrapper implementation which delegates to the ClassHelper
+ * internal methods.
+ *
+ */
+ private static class DefaultClassUnwrapper implements ClassUnwrapper {
+ private final ClassHelper helper;
+
+ DefaultClassUnwrapper(ClassHelper helper) {
+ this.helper = helper;
+ }
+
+ @Override
+ public Class<?> getRealClassFromClass(Class<?> clazz) {
+ return helper.getRealClassFromClassInternal(clazz);
+ }
+
+ @Override
+ public Class<?> getRealClass(Object o) {
+ return helper.getRealClassInternal(o);
+ }
+
+ @Override
+ public Object getRealObject(Object o) {
+ return helper.getRealObjectInternal(o);
+ }
+ }
+
+ static {
+ HELPER = new ClassHelper();
+ DEFAULT_UNWRAPPER = new DefaultClassUnwrapper(HELPER);
+ UNWRAPPER = getClassUnwrapper(DEFAULT_UNWRAPPER);
+ }
+
+ protected ClassHelper() {
+ }
+
+ private static ClassUnwrapper getClassUnwrapper(ClassUnwrapper defaultHelper) {
+ boolean useSpring = true;
+ String s = SystemPropertyAction.getPropertyOrNull("org.apache.cxf.useSpringClassHelpers");
+ if (!StringUtils.isEmpty(s)) {
+ useSpring = "1".equals(s) || Boolean.parseBoolean(s);
+ }
+ if (useSpring) {
+ try {
+ return new SpringClassUnwrapper();
+ } catch (Throwable ex) {
+ // ignore
+ }
+ }
+ return defaultHelper;
+ }
+
+ private Class<?> getRealClassInternal(Object o) {
+ return getRealObjectInternal(o).getClass();
+ }
+
+ private Class<?> getRealClassFromClassInternal(Class<?> cls) {
+ return cls;
+ }
+
+ private Object getRealObjectInternal(Object o) {
+ return o instanceof Proxy ? Proxy.getInvocationHandler(o) : o;
+ }
+
+ public static Class<?> getRealClass(Object o) {
+ return getRealClass(null, o);
+ }
+
+ public static Class<?> getRealClassFromClass(Class<?> cls) {
+ return getRealClassFromClass(null, cls);
+ }
+
+ public static Class<?> getRealClassFromClass(Bus bus, Class<?> cls) {
+ return getContextClassUnwrapper(getBus(bus)).getRealClassFromClass(cls);
+ }
+
+ public static Object getRealObject(Object o) {
+ return getContextClassUnwrapper(getBus(null)).getRealObject(o);
+ }
+
+ public static Class<?> getRealClass(Bus bus, Object o) {
+ return getContextClassUnwrapper(getBus(bus)).getRealClass(o);
+ }
+
+ private static ClassUnwrapper getContextClassUnwrapper(Bus bus) {
+ if (bus != null && bus.getProperty(ClassUnwrapper.class.getName()) != null) {
+ return (ClassUnwrapper) bus.getProperty(ClassUnwrapper.class.getName());
+ }
+
+ return (DEFAULT_UNWRAPPER == UNWRAPPER || checkUseDefaultClassHelper(bus)) ? DEFAULT_UNWRAPPER : UNWRAPPER;
+ }
+
+ private static Bus getBus(Bus bus) {
+ return bus == null ? BusFactory.getThreadDefaultBus() : bus;
+ }
+
+ private static boolean checkUseDefaultClassHelper(Bus bus) {
+ return bus != null && Boolean.TRUE.equals(bus.getProperty(USE_DEFAULT_CLASS_HELPER));
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/util/CollectionUtils.java b/transform/src/patch/java/org/apache/cxf/common/util/CollectionUtils.java
new file mode 100644
index 0000000..4e888c3
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/util/CollectionUtils.java
@@ -0,0 +1,126 @@
+/**
+ * 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.common.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+
+public final class CollectionUtils {
+ private CollectionUtils() {
+
+ }
+
+ public static <T> Collection<T> diff(Collection<T> c1, Collection<T> c2) {
+ if (c1 == null || c1.isEmpty() || c2 == null || c2.isEmpty()) {
+ return c1;
+ }
+ Collection<T> difference = new ArrayList<>();
+ for (T item : c1) {
+ if (!c2.contains(item)) {
+ difference.add(item);
+ }
+ }
+ return difference;
+ }
+
+ public static <T> boolean isEmpty(Collection<T> c) {
+ if (c != null && !c.isEmpty()) {
+ for (T item : c) {
+ if (item != null) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public static <K, V> boolean isEmpty(Map<K, V> m) {
+ return m == null || m.isEmpty();
+ }
+
+ public static <S, T> Dictionary<S, T> singletonDictionary(S s, T t) {
+ return toDictionary(Collections.singletonMap(s, t));
+ }
+
+ public static <S, T> Dictionary<S, T> toDictionary(Map<S, T> map) {
+ return new MapToDictionary<>(map);
+ }
+
+ static class MapToDictionary<S, T> extends Dictionary<S, T> {
+ /**
+ * Map source.
+ **/
+ private final Map<S, T> map;
+
+ MapToDictionary(Map<S, T> map) {
+ this.map = map;
+ }
+
+
+ public Enumeration<T> elements() {
+ return map != null ? new IteratorToEnumeration<>(map.values().iterator()) : null;
+ }
+
+ public T get(Object key) {
+ return map != null ? map.get(key) : null;
+ }
+
+ public boolean isEmpty() {
+ return map == null || map.isEmpty();
+ }
+
+ public Enumeration<S> keys() {
+ return map != null ? new IteratorToEnumeration<>(map.keySet().iterator()) : null;
+ }
+
+ public T put(S key, T value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public T remove(Object key) {
+ throw new UnsupportedOperationException();
+ }
+
+ public int size() {
+ return map != null ? map.size() : 0;
+ }
+
+ static class IteratorToEnumeration<X> implements Enumeration<X> {
+ private final Iterator<X> iter;
+
+ IteratorToEnumeration(Iterator<X> iter) {
+ this.iter = iter;
+ }
+
+ public boolean hasMoreElements() {
+ return iter != null && iter.hasNext();
+ }
+
+ public X nextElement() {
+ return iter != null ? iter.next() : null;
+ }
+ }
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/util/Compiler.java b/transform/src/patch/java/org/apache/cxf/common/util/Compiler.java
new file mode 100644
index 0000000..9d0c819
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/util/Compiler.java
@@ -0,0 +1,382 @@
+/**
+ * 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.common.util;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticListener;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaCompiler.CompilationTask;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+
+import org.apache.cxf.helpers.FileUtils;
+
+public class Compiler {
+ private long maxMemory = Runtime.getRuntime().maxMemory();
+ private boolean verbose;
+ private String target;
+ private String outputDir;
+ private String classPath;
+ private String encoding;
+ private boolean forceFork = Boolean.getBoolean(Compiler.class.getName() + "-fork");
+ private File classpathTmpFile;
+ private List<String> errors = new LinkedList<>();
+ private List<String> warnings = new LinkedList<>();
+
+ public Compiler() {
+ }
+
+ public List<String> getErrors() {
+ return errors;
+ }
+ public List<String> getWarnings() {
+ return warnings;
+ }
+
+ public void setMaxMemory(long l) {
+ maxMemory = l;
+ }
+ public void setVerbose(boolean b) {
+ verbose = b;
+ }
+ public void setTarget(String s) {
+ target = s;
+ }
+ public void setOutputDir(File s) {
+ if (s != null) {
+ outputDir = s.getAbsolutePath().replace(File.pathSeparatorChar, '/');
+ } else {
+ outputDir = null;
+ }
+ }
+ public void setOutputDir(String s) {
+ outputDir = s.replace(File.pathSeparatorChar, '/');
+ }
+ public void setClassPath(String s) {
+ classPath = StringUtils.isEmpty(s) ? null : s;
+ }
+
+ // https://issues.apache.org/jira/browse/CXF-8049
+ private String getSystemClassPath() {
+ String javaClasspath = SystemPropertyAction.getProperty("java.class.path");
+
+ if (!StringUtils.isEmpty(javaClasspath)) {
+ List<String> correctedEntries = new ArrayList<>();
+
+ String[] toks = javaClasspath.split(File.pathSeparator);
+
+ for (String tok: toks) {
+ // if any classpath entry contains a whitespace char,
+ // enclose the entry in double quotes
+ if (tok.matches(".*\\s+.*")) {
+ correctedEntries.add("\"" + tok + "\"");
+ } else {
+ correctedEntries.add(tok);
+ }
+ }
+
+ return String.join(File.pathSeparator, correctedEntries);
+ }
+
+ return javaClasspath;
+ }
+
+ protected void addArgs(List<String> list) {
+ if (!StringUtils.isEmpty(encoding)) {
+ list.add("-encoding");
+ list.add(encoding);
+ }
+ if (!StringUtils.isEmpty(target)) {
+ list.add("-target");
+ list.add(target);
+ list.add("-source");
+ list.add(target);
+ }
+
+ if (!StringUtils.isEmpty(outputDir)) {
+ list.add("-d");
+ list.add(outputDir);
+ }
+
+ if (StringUtils.isEmpty(classPath)) {
+ String javaClasspath = getSystemClassPath();
+ boolean classpathSetted = !StringUtils.isEmpty(javaClasspath);
+ if (!classpathSetted) {
+ File f = new File(getClass().getClassLoader().getResource(".").getFile());
+ f = new File(f, "../lib");
+ if (f.exists() && f.isDirectory()) {
+ list.add("-extdirs");
+ list.add(f.toString());
+ }
+ } else {
+ list.add("-classpath");
+ list.add(javaClasspath);
+ }
+ } else {
+ list.add("-classpath");
+ list.add(classPath);
+ }
+
+ }
+ public boolean compileFiles(File[] files) {
+ List<String> f = new ArrayList<>(files.length);
+ for (File file : files) {
+ f.add(file.getAbsolutePath());
+ }
+ return compileFiles(f.toArray(new String[0]));
+ }
+ public boolean compileFiles(List<File> files) {
+ List<String> f = new ArrayList<>(files.size());
+ for (File file : files) {
+ f.add(file.getAbsolutePath());
+ }
+ return compileFiles(f.toArray(new String[0]));
+ }
+ public boolean compileFiles(String[] files) {
+ String endorsed = SystemPropertyAction.getProperty("java.endorsed.dirs");
+ if (!forceFork) {
+ return useJava6Compiler(files);
+ }
+
+ List<String> list = new ArrayList<>();
+
+ // Start of honoring java.home for used javac
+ String fsep = File.separator;
+ String javacstr = "javac";
+ String platformjavacname = "javac";
+
+ if (SystemPropertyAction.getProperty("os.name").toLowerCase().indexOf("windows") > -1) {
+ platformjavacname = "javac.exe";
+ }
+
+ if (new File(SystemPropertyAction.getProperty("java.home") + fsep + platformjavacname).exists()) {
+ // check if java.home is jdk home
+ javacstr = SystemPropertyAction.getProperty("java.home") + fsep + platformjavacname;
+ } else if (new File(SystemPropertyAction.getProperty("java.home") + fsep + ".." + fsep + "bin" + fsep
+ + platformjavacname).exists()) {
+ // check if java.home is jre home
+ javacstr = SystemPropertyAction.getProperty("java.home") + fsep + ".." + fsep + "bin" + fsep
+ + platformjavacname;
+ } else if (new File(SystemPropertyAction.getProperty("java.home") + fsep + "bin" + fsep
+ + platformjavacname).exists()) {
+ //java9
+ javacstr = SystemPropertyAction.getProperty("java.home") + fsep + "bin" + fsep
+ + platformjavacname;
+ }
+ list.add(javacstr);
+ // End of honoring java.home for used javac
+
+ if (!StringUtils.isEmpty(endorsed)) {
+ list.add("-endorseddirs");
+ list.add(endorsed);
+ }
+
+ //fix for CXF-2081, set maximum heap of this VM to javac.
+ list.add("-J-Xmx" + maxMemory);
+
+ addArgs(list);
+ int classpathIdx = list.indexOf("-classpath");
+ String classpath = list.get(classpathIdx + 1);
+ checkLongClasspath(classpath, list, classpathIdx);
+ int idx = list.size();
+ Collections.addAll(list, files);
+
+ return internalCompile(list.toArray(new String[0]), idx);
+ }
+
+ protected boolean useJava6Compiler(String[] files) {
+ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+ if (compiler == null) {
+ throw new IllegalStateException(
+ "No compiler detected, make sure you are running on top of a JDK instead of a JRE.");
+ }
+ StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
+ Iterable<? extends JavaFileObject> fileList = fileManager.getJavaFileObjectsFromStrings(Arrays
+ .asList(files));
+
+ return internalJava6Compile(compiler, wrapJavaFileManager(fileManager), setupDiagnosticListener(),
+ fileList);
+ }
+
+ protected JavaFileManager wrapJavaFileManager(StandardJavaFileManager standardJavaFileManger) {
+ return standardJavaFileManger;
+ }
+
+ protected DiagnosticListener<JavaFileObject> setupDiagnosticListener() {
+ return new DiagnosticListener<JavaFileObject>() {
+ public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
+ switch (diagnostic.getKind()) {
+ case ERROR:
+ errors.add(diagnostic.toString());
+ if (verbose) {
+ System.err.println(diagnostic.toString());
+ }
+ break;
+ case WARNING:
+ case MANDATORY_WARNING:
+ warnings.add(diagnostic.toString());
+ if (verbose) {
+ System.err.println(diagnostic.toString());
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ };
+ }
+
+ protected boolean internalJava6Compile(JavaCompiler compiler, JavaFileManager fileManager,
+ DiagnosticListener<JavaFileObject> listener,
+ Iterable<? extends JavaFileObject> fileList) {
+ List<String> args = new ArrayList<>();
+ addArgs(args);
+ CompilationTask task = compiler.getTask(null, fileManager, listener, args, null, fileList);
+ Boolean ret = task.call();
+ try {
+ fileManager.close();
+ } catch (IOException e) {
+ System.err.print("[ERROR] IOException during compiling.");
+ e.printStackTrace();
+ }
+ return ret;
+ }
+
+ public boolean internalCompile(String[] args, int sourceFileIndex) {
+ File tmpFile = null;
+ try {
+ final String[] cmdArray;
+ if (isLongCommandLines(args) && sourceFileIndex >= 0) {
+ tmpFile = FileUtils.createTempFile("cxf-compiler", null);
+ try (PrintWriter out = new PrintWriter(new FileWriter(tmpFile))) {
+ for (int i = sourceFileIndex; i < args.length; i++) {
+ if (args[i].indexOf(' ') > -1) {
+ args[i] = args[i].replace(File.separatorChar, '/');
+ //
+ // javac gives an error if you use forward slashes
+ // with package-info.java. Refer to:
+ // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6198196
+ //
+ if (args[i].indexOf("package-info.java") > -1
+ && SystemPropertyAction.getProperty("os.name")
+ .toLowerCase().indexOf("windows") > -1) {
+ out.println('"' + args[i].replaceAll("/", "\\\\\\\\") + '"');
+ } else {
+ out.println('"' + args[i] + '"');
+ }
+ } else {
+ out.println(args[i]);
+ }
+ }
+ out.flush();
+ }
+ cmdArray = new String[sourceFileIndex + 1];
+ System.arraycopy(args, 0, cmdArray, 0, sourceFileIndex);
+ cmdArray[sourceFileIndex] = "@" + tmpFile;
+ } else {
+ cmdArray = new String[args.length];
+ System.arraycopy(args, 0, cmdArray, 0, args.length);
+ }
+
+ if (SystemPropertyAction.getProperty("os.name").toLowerCase().indexOf("windows") > -1) {
+ for (int i = 0; i < cmdArray.length; i++) {
+ if (cmdArray[i].indexOf("package-info") == -1) {
+ cmdArray[i] = cmdArray[i].replace('\\', '/');
+ }
+ }
+ }
+
+ final Process p = Runtime.getRuntime().exec(cmdArray);
+
+ if (p.getErrorStream() != null) {
+ StreamPrinter errorStreamPrinter = new StreamPrinter(p.getErrorStream(), "", System.out);
+ errorStreamPrinter.start();
+ }
+
+ if (p.getInputStream() != null) {
+ StreamPrinter infoStreamPrinter = new StreamPrinter(p.getInputStream(), "[INFO]", System.out);
+ infoStreamPrinter.start();
+ }
+
+ return p.waitFor() == 0 ? true : false;
+ } catch (SecurityException e) {
+ System.err.println("[ERROR] SecurityException during exec() of compiler \"" + args[0] + "\".");
+ } catch (InterruptedException e) {
+ // ignore
+
+ } catch (IOException e) {
+ System.err.print("[ERROR] IOException during exec() of compiler \"" + args[0] + "\"");
+ System.err.println(". Check your path environment variable.");
+ } finally {
+ if (tmpFile != null && tmpFile.exists()) {
+ FileUtils.delete(tmpFile);
+ }
+ if (classpathTmpFile != null && classpathTmpFile.exists()) {
+ FileUtils.delete(classpathTmpFile);
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isLongCommandLines(String[] args) {
+ StringBuilder strBuffer = new StringBuilder();
+ for (int i = 0; i < args.length; i++) {
+ strBuffer.append(args[i]);
+ }
+ return strBuffer.length() > 4096;
+ }
+
+ private boolean isLongClasspath(String classpath) {
+ return classpath.length() > 2048;
+ }
+
+ private void checkLongClasspath(String classpath, List<String> list, int classpathIdx) {
+ if (isLongClasspath(classpath)) {
+ try {
+ classpathTmpFile = FileUtils.createTempFile("cxf-compiler-classpath", null);
+ try (PrintWriter out = new PrintWriter(new FileWriter(classpathTmpFile))) {
+ out.println(classpath);
+ out.flush();
+ }
+ list.set(classpathIdx + 1, "@" + classpathTmpFile);
+ } catch (IOException e) {
+ System.err.print("[ERROR] can't write long classpath to @argfile");
+ }
+ }
+ }
+
+ public void setEncoding(String string) {
+ encoding = string;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/util/ModCountCopyOnWriteArrayList.java b/transform/src/patch/java/org/apache/cxf/common/util/ModCountCopyOnWriteArrayList.java
new file mode 100644
index 0000000..daa6407
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/util/ModCountCopyOnWriteArrayList.java
@@ -0,0 +1,156 @@
+/**
+ * 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.common.util;
+
+import java.util.Collection;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public final class ModCountCopyOnWriteArrayList<T> extends CopyOnWriteArrayList<T> {
+ private static final long serialVersionUID = 1783937035760941219L;
+ private AtomicInteger modCount = new AtomicInteger();
+
+ public ModCountCopyOnWriteArrayList() {
+ super();
+ }
+ public ModCountCopyOnWriteArrayList(Collection<? extends T> c) {
+ super(c);
+ if (c instanceof ModCountCopyOnWriteArrayList) {
+ modCount.set(((ModCountCopyOnWriteArrayList<?>)c).getModCount());
+ }
+ }
+
+ public int getModCount() {
+ return modCount.get();
+ }
+
+ public void setModCount(int i) {
+ modCount.set(i);
+ }
+
+ @Override
+ public void add(int index, T element) {
+ super.add(index, element);
+ modCount.incrementAndGet();
+ }
+
+ @Override
+ public boolean add(T element) {
+ if (super.add(element)) {
+ modCount.incrementAndGet();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends T> c) {
+ if (super.addAll(c)) {
+ modCount.incrementAndGet();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean addAll(int index, Collection<? extends T> c) {
+ if (super.addAll(index, c)) {
+ modCount.incrementAndGet();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int addAllAbsent(Collection<? extends T> c) {
+ int i = super.addAllAbsent(c);
+ if (i > 0) {
+ modCount.incrementAndGet();
+ }
+ return i;
+ }
+
+ @Override
+ public boolean addIfAbsent(T element) {
+ if (super.addIfAbsent(element)) {
+ modCount.incrementAndGet();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ modCount.incrementAndGet();
+ }
+
+ @Override
+ public T remove(int index) {
+ T t = super.remove(index);
+ if (t != null) {
+ modCount.incrementAndGet();
+ }
+ return t;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ if (super.remove(o)) {
+ modCount.incrementAndGet();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> c) {
+ if (super.removeAll(c)) {
+ modCount.incrementAndGet();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> c) {
+ if (super.retainAll(c)) {
+ modCount.incrementAndGet();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + modCount.get();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof ModCountCopyOnWriteArrayList) {
+ return super.equals(o) && modCount.get()
+ == ((ModCountCopyOnWriteArrayList<?>)o).getModCount();
+ }
+ return false;
+ }
+
+}
+
diff --git a/transform/src/patch/java/org/apache/cxf/common/util/PackageUtils.java b/transform/src/patch/java/org/apache/cxf/common/util/PackageUtils.java
new file mode 100644
index 0000000..9d014c8
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/util/PackageUtils.java
@@ -0,0 +1,181 @@
+/**
+ * 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.common.util;
+
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.apache.cxf.helpers.JavaUtils;
+
+
+public final class PackageUtils {
+
+ private PackageUtils() {
+
+ }
+
+ static String getPackageName(String className) {
+ int pos = className.lastIndexOf('.');
+ if (pos != -1) {
+ return className.substring(0, pos);
+ }
+ return "";
+ }
+
+ public static String getPackageName(Class<?> clazz) {
+ String className = clazz.getName();
+ if (className.startsWith("[L")) {
+ className = className.substring(2);
+ }
+ return getPackageName(className);
+ }
+
+ public static String getSharedPackageName(List<Class<?>> classes) {
+ if (classes.isEmpty()) {
+ return "";
+ }
+ List<List<String>> lParts = new ArrayList<>(classes.size());
+ List<String> currentParts = new ArrayList<>();
+ for (Class<?> cls : classes) {
+ if (!Proxy.isProxyClass(cls)) {
+ lParts.add(Arrays.asList(getPackageName(cls).split("\\.")));
+ }
+ }
+ for (int i = 0; i < lParts.get(0).size(); i++) {
+ int j = 1;
+ for (; j < lParts.size(); j++) {
+ if (i > (lParts.get(j).size() - 1) || !lParts.get(j).get(i).equals(lParts.get(0).get(i))) {
+ break;
+ }
+ }
+ if (j == lParts.size()) {
+ currentParts.add(lParts.get(j - 1).get(i));
+ } else {
+ break;
+ }
+ }
+ return String.join(".", currentParts);
+ }
+
+ public static String parsePackageName(String namespace, String defaultPackageName) {
+ return (defaultPackageName != null && !defaultPackageName.trim().isEmpty())
+ ? defaultPackageName : getPackageNameByNameSpaceURI(namespace.trim());
+ }
+
+ public static String getPackageNameByNameSpaceURI(String nameSpaceURI) {
+ int idx = nameSpaceURI.indexOf(':');
+ boolean urnScheme = false;
+ if (idx >= 0) {
+ final String scheme = nameSpaceURI.substring(0, idx);
+ urnScheme = "urn".equalsIgnoreCase(scheme);
+ if ("http".equalsIgnoreCase(scheme) || urnScheme) {
+ nameSpaceURI = nameSpaceURI.substring(idx + (urnScheme ? 1 : 3)); //
+ }
+ }
+
+ List<String> tokens = tokenize(nameSpaceURI, "/:");
+ if (tokens.isEmpty()) {
+ return null;
+ }
+
+ if (tokens.size() > 1) {
+ String lastToken = tokens.get(tokens.size() - 1);
+ idx = lastToken.lastIndexOf('.');
+ if (idx > 0) {
+ lastToken = lastToken.replace('.', '_');
+ tokens.set(tokens.size() - 1, lastToken);
+ }
+ }
+
+ String domain = tokens.remove(0);
+ List<String> r = tokenize(domain, urnScheme ? ".-" : ".");
+ Collections.reverse(r);
+ if ("www".equalsIgnoreCase(r.get(r.size() - 1))) {
+ // remove leading www
+ r.remove(r.size() - 1);
+ }
+
+ // replace the domain name with tokenized items
+ tokens.addAll(0, r);
+
+ // iterate through the tokens and apply xml->java name algorithm
+ for (int i = 0; i < tokens.size(); i++) {
+
+ // get the token and remove illegal chars
+ String token = tokens.get(i);
+ token = removeIllegalIdentifierChars(token);
+
+ token = token.toLowerCase();
+
+ // this will check for reserved keywords
+ if (JavaUtils.isJavaKeyword(token)) {
+ token = '_' + token;
+ }
+
+ tokens.set(i, token);
+ }
+
+ // concat all the pieces and return it
+ return String.join(".", tokens);
+ }
+
+ private static List<String> tokenize(String str, String sep) {
+ StringTokenizer tokens = new StringTokenizer(str, sep);
+ List<String> r = new ArrayList<>();
+
+ while (tokens.hasMoreTokens()) {
+ r.add(tokens.nextToken());
+ }
+ return r;
+ }
+
+ private static String removeIllegalIdentifierChars(String token) {
+ StringBuilder newToken = new StringBuilder();
+ for (int i = 0; i < token.length(); i++) {
+ char c = token.charAt(i);
+
+ if (i == 0 && !Character.isJavaIdentifierStart(c)) {
+ // prefix an '_' if the first char is illegal
+ newToken.append('_').append(c);
+ } else if (!Character.isJavaIdentifierPart(c)) {
+ // replace the char with an '_' if it is illegal
+ newToken.append('_');
+ } else {
+ // add the legal char
+ newToken.append(c);
+ }
+ }
+ return newToken.toString();
+ }
+
+ public static String getNamespace(String packageName) {
+ if (packageName == null || packageName.isEmpty()) {
+ return null;
+ }
+ final List<String> parts = Arrays.asList(packageName.split("\\."));
+ Collections.reverse(parts);
+ return "http://" + String.join(".", parts) + '/';
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/util/ProxyClassLoader.java b/transform/src/patch/java/org/apache/cxf/common/util/ProxyClassLoader.java
new file mode 100644
index 0000000..073ca46
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/util/ProxyClassLoader.java
@@ -0,0 +1,89 @@
+/**
+ * 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.common.util;
+
+import java.net.URL;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Utility class loader that can be used to create proxies in cases where
+ * the the client classes are not visible to the loader of the
+ * service class.
+ */
+public class ProxyClassLoader extends ClassLoader {
+ private final Class<?>[] classes;
+ private final Set<ClassLoader> loaders = new HashSet<>();
+ private boolean checkSystem;
+
+ public ProxyClassLoader(ClassLoader parent) {
+ super(parent);
+ classes = null;
+ }
+
+ public ProxyClassLoader(ClassLoader parent, Class<?>[] cls) {
+ super(parent);
+ classes = cls;
+ }
+
+ public void addLoader(ClassLoader loader) {
+ if (loader == null) {
+ checkSystem = true;
+ } else {
+ loaders.add(loader);
+ }
+ }
+
+ @Override
+ public Class<?> findClass(String name) throws ClassNotFoundException {
+ if (classes != null) {
+ for (Class<?> c : classes) {
+ if (name.equals(c.getName())) {
+ return c;
+ }
+ }
+ }
+ for (ClassLoader loader : loaders) {
+ try {
+ return loader.loadClass(name);
+ } catch (ClassNotFoundException | NoClassDefFoundError cnfe) {
+ // Try next
+ }
+ }
+ if (checkSystem) {
+ try {
+ return getSystemClassLoader().loadClass(name);
+ } catch (ClassNotFoundException | NoClassDefFoundError cnfe) {
+ // Try next
+ }
+ }
+ throw new ClassNotFoundException(name);
+ }
+
+ @Override
+ public URL findResource(String name) {
+ for (ClassLoader loader : loaders) {
+ URL url = loader.getResource(name);
+ if (url != null) {
+ return url;
+ }
+ }
+ return null;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/util/ProxyHelper.java b/transform/src/patch/java/org/apache/cxf/common/util/ProxyHelper.java
new file mode 100644
index 0000000..380e2bb
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/util/ProxyHelper.java
@@ -0,0 +1,140 @@
+/**
+ * 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.common.util;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.logging.LogUtils;
+
+/**
+ *
+ */
+public class ProxyHelper {
+ static final ProxyHelper HELPER;
+ static {
+ ProxyHelper theHelper;
+ try {
+ theHelper = new CglibProxyHelper();
+ } catch (Throwable ex) {
+ theHelper = new ProxyHelper();
+ }
+ HELPER = theHelper;
+ }
+
+ private static final Logger LOG = LogUtils.getL7dLogger(ProxyHelper.class);
+
+ protected ProxyClassLoaderCache proxyClassLoaderCache =
+ new ProxyClassLoaderCache();
+
+
+ protected ProxyHelper() {
+ }
+
+ protected Object getProxyInternal(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) {
+ ClassLoader combinedLoader = getClassLoaderForInterfaces(loader, interfaces);
+ return Proxy.newProxyInstance(combinedLoader, interfaces, handler);
+ }
+
+ /**
+ * Return a classloader that can see all the given interfaces If the given loader can see all interfaces
+ * then it is used. If not then a combined classloader of all interface classloaders is returned.
+ *
+ * @param loader use supplied class loader
+ * @param interfaces
+ * @return classloader that sees all interfaces
+ */
+ private ClassLoader getClassLoaderForInterfaces(final ClassLoader loader, final Class<?>[] interfaces) {
+ if (canSeeAllInterfaces(loader, interfaces)) {
+ LOG.log(Level.FINE, "current classloader " + loader + " can see all interface");
+ return loader;
+ }
+ String sortedNameFromInterfaceArray = getSortedNameFromInterfaceArray(interfaces);
+ ClassLoader cachedLoader = proxyClassLoaderCache.getProxyClassLoader(loader, interfaces);
+ if (canSeeAllInterfaces(cachedLoader, interfaces)) {
+ LOG.log(Level.FINE, "find required loader from ProxyClassLoader cache with key"
+ + sortedNameFromInterfaceArray);
+ return cachedLoader;
+ } else {
+ LOG.log(Level.FINE, "find a loader from ProxyClassLoader cache with interfaces "
+ + sortedNameFromInterfaceArray
+ + " but can't see all interfaces");
+ for (Class<?> currentInterface : interfaces) {
+ String ifName = currentInterface.getName();
+
+ if (!ifName.startsWith("org.apache.cxf") && !ifName.startsWith("java")) {
+ // remove the stale ProxyClassLoader and recreate one
+ proxyClassLoaderCache.removeStaleProxyClassLoader(currentInterface);
+ cachedLoader = proxyClassLoaderCache.getProxyClassLoader(loader, interfaces);
+
+ }
+ }
+ }
+
+ return cachedLoader;
+ }
+
+ private String getSortedNameFromInterfaceArray(Class<?>[] interfaces) {
+ SortedArraySet<String> arraySet = new SortedArraySet<>();
+ for (Class<?> currentInterface : interfaces) {
+ arraySet.add(currentInterface.getName() + ClassLoaderUtils.getClassLoaderName(currentInterface));
+ }
+ return arraySet.toString();
+ }
+
+
+ private boolean canSeeAllInterfaces(ClassLoader loader, Class<?>[] interfaces) {
+ for (Class<?> currentInterface : interfaces) {
+ String ifName = currentInterface.getName();
+ try {
+ Class<?> ifClass = Class.forName(ifName, true, loader);
+ if (ifClass != currentInterface) {
+ return false;
+ }
+ //we need to check all the params/returns as well as the Proxy creation
+ //will try to create methods for all of this even if they aren't used
+ //by the client and not available in the clients classloader
+ for (Method m : ifClass.getMethods()) {
+ Class<?> returnType = m.getReturnType();
+ if (!returnType.isPrimitive()) {
+ Class.forName(returnType.getName(), true, loader);
+ }
+ for (Class<?> p : m.getParameterTypes()) {
+ if (!p.isPrimitive()) {
+ Class.forName(p.getName(), true, loader);
+ }
+ }
+ }
+ } catch (NoClassDefFoundError | ClassNotFoundException e) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static Object getProxy(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) {
+ return HELPER.getProxyInternal(loader, interfaces, handler);
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/util/ReflectionInvokationHandler.java b/transform/src/patch/java/org/apache/cxf/common/util/ReflectionInvokationHandler.java
new file mode 100644
index 0000000..1b4d904
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/util/ReflectionInvokationHandler.java
@@ -0,0 +1,199 @@
+/**
+ * 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.common.util;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Iterator;
+
+/**
+ *
+ */
+public class ReflectionInvokationHandler implements InvocationHandler {
+ private Object target;
+
+ public ReflectionInvokationHandler(Object obj) {
+ target = obj;
+ }
+
+ public Object getTarget() {
+ return target;
+ }
+
+ /** {@inheritDoc}*/
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ WrapReturn wr = method.getAnnotation(WrapReturn.class);
+ final Class<?> targetClass = target.getClass();
+ final Class<?>[] parameterTypes = getParameterTypes(method, args);
+ try {
+ Method m;
+ try {
+ m = targetClass.getMethod(method.getName(), parameterTypes);
+ } catch (NoSuchMethodException nsme) {
+
+ boolean[] optionals = new boolean[method.getParameterTypes().length];
+ int i = 0;
+ int optionalNumber = 0;
+ for (final Annotation[] a : method.getParameterAnnotations()) {
+ optionals[i] = false;
+ for (final Annotation potential : a) {
+ if (Optional.class.equals(potential.annotationType())) {
+ optionals[i] = true;
+ optionalNumber++;
+ break;
+ }
+ }
+ i++;
+ }
+
+ Class<?>[] newParams = new Class<?>[args.length - optionalNumber];
+ Object[] newArgs = new Object[args.length - optionalNumber];
+ int argI = 0;
+ for (int j = 0; j < parameterTypes.length; j++) {
+ if (optionals[j]) {
+ continue;
+ }
+ newArgs[argI] = args[j];
+ newParams[argI] = parameterTypes[j];
+ argI++;
+ }
+ m = targetClass.getMethod(method.getName(), newParams);
+ args = newArgs;
+ }
+ ReflectionUtil.setAccessible(m);
+ return wrapReturn(wr, m.invoke(target, args));
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ } catch (NoSuchMethodException e) {
+ for (Method m2 : targetClass.getMethods()) {
+ if (m2.getName().equals(method.getName())
+ && m2.getParameterTypes().length == method.getParameterTypes().length) {
+ boolean found = true;
+ for (int x = 0; x < m2.getParameterTypes().length; x++) {
+ if (args[x] != null
+ && !m2.getParameterTypes()[x].isInstance(args[x])) {
+ found = false;
+ }
+ }
+ if (found) {
+ ReflectionUtil.setAccessible(m2);
+ return wrapReturn(wr, m2.invoke(target, args));
+ }
+ }
+ }
+ throw e;
+ }
+ }
+ private Class<?>[] getParameterTypes(Method method, Object[] args) {
+ Class<?>[] types = method.getParameterTypes();
+ final Annotation[][] parAnnotations = method.getParameterAnnotations();
+ for (int x = 0; x < types.length; x++) {
+ UnwrapParam p = getUnwrapParam(parAnnotations[x]);
+ if (p != null) {
+ String s = p.methodName();
+ String tn = p.typeMethodName();
+ try {
+ Method m = args[x].getClass().getMethod(s);
+ if ("#default".equals(tn)) {
+ types[x] = m.getReturnType();
+ } else {
+ Method m2 = args[x].getClass().getMethod(tn);
+ types[x] = (Class<?>)ReflectionUtil.setAccessible(m2).invoke(args[x]);
+ }
+ args[x] = ReflectionUtil.setAccessible(m).invoke(args[x]);
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+ return types;
+ }
+
+ private UnwrapParam getUnwrapParam(Annotation[] annotations) {
+ for (Annotation a : annotations) {
+ if (a instanceof UnwrapParam) {
+ return (UnwrapParam)a;
+ }
+ }
+ return null;
+ }
+
+ private static Object wrapReturn(WrapReturn wr, Object t) {
+ if (wr == null || t == null) {
+ return t;
+ }
+ if (wr.iterator()) {
+ return new WrapperIterator(wr.value(), (Iterator<?>)t);
+ }
+ return createProxyWrapper(t, wr.value());
+ }
+
+ public static <T> T createProxyWrapper(Object target, Class<T> inf) {
+ InvocationHandler h = new ReflectionInvokationHandler(target);
+ return inf.cast(Proxy.newProxyInstance(inf.getClassLoader(), new Class<?>[] {inf}, h));
+ }
+
+ @Target(ElementType.PARAMETER)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Optional {
+ }
+
+ @Target(ElementType.METHOD)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface WrapReturn {
+ Class<?> value();
+ boolean iterator() default false;
+ }
+
+ @Target(ElementType.PARAMETER)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface UnwrapParam {
+ String methodName() default "getValue";
+ String typeMethodName() default "#default";
+ }
+
+ private static class WrapperIterator implements Iterator<Object> {
+ Class<?> cls;
+ Iterator<?> internal;
+ WrapperIterator(Class<?> c, Iterator<?> it) {
+ internal = it;
+ cls = c;
+ }
+ public boolean hasNext() {
+ return internal.hasNext();
+ }
+ public Object next() {
+ Object obj = internal.next();
+ return createProxyWrapper(obj, cls);
+ }
+
+ @Override
+ public void remove() {
+ internal.remove();
+ }
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/util/SortedArraySet.java b/transform/src/patch/java/org/apache/cxf/common/util/SortedArraySet.java
new file mode 100644
index 0000000..9c2b139
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/util/SortedArraySet.java
@@ -0,0 +1,266 @@
+/**
+ * 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.common.util;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.SortedSet;
+import java.util.concurrent.atomic.AtomicReference;
+
+
+/**
+ * This class implements most of the <tt>Set</tt> interface, backed by a
+ * sorted Array. This makes iterators very fast, lookups are log(n), but
+ * adds are fairly expensive.
+ *
+ * This class is also threadsafe, but without synchronizations. Lookups
+ * and iterators will iterate over the state of the Set when the iterator
+ * was created.
+ *
+ * If no data is stored in the Set, it uses very little memory. The backing
+ * array is created on demand.
+ *
+ * This class is primarly useful for stuff that will be setup at startup, but
+ * then iterated over MANY times during runtime.
+ *
+ * @param <T>
+ */
+public final class SortedArraySet<T> implements SortedSet<T> {
+ final AtomicReference<T[]> data = new AtomicReference<>();
+
+ public void clear() {
+ data.set(null);
+ }
+
+ public boolean isEmpty() {
+ T[] tmp = data.get();
+ return tmp == null || tmp.length == 0;
+ }
+
+ public Iterator<T> iterator() {
+ return new SASIterator<>(data.get());
+ }
+
+ public int size() {
+ T[] tmp = data.get();
+ return tmp == null ? 0 : tmp.length;
+ }
+
+ @SuppressWarnings("unchecked")
+ private T[] newArray(int size) {
+ return (T[])new Object[size];
+ }
+
+ public boolean add(T o) {
+ if (!contains(o)) {
+ T[] tmp = data.get();
+ T[] tmp2;
+ if (tmp == null) {
+ tmp2 = newArray(1);
+ tmp2[0] = o;
+ } else {
+ tmp2 = newArray(tmp.length + 1);
+ System.arraycopy(tmp, 0, tmp2, 0, tmp.length);
+ tmp2[tmp2.length - 1] = o;
+ Arrays.sort(tmp2);
+ }
+
+ if (!data.compareAndSet(tmp, tmp2)) {
+ return add(o);
+ }
+ return true;
+ }
+ return false;
+ }
+ public boolean addAll(Collection<? extends T> c) {
+ boolean val = false;
+ for (T t : c) {
+ val |= add(t);
+ }
+ return val;
+ }
+ public boolean containsAll(Collection<?> c) {
+ boolean val = false;
+ for (Object t : c) {
+ val |= contains(t);
+ }
+ return val;
+ }
+
+ public boolean contains(Object o) {
+ T[] tmp = data.get();
+ if (tmp == null) {
+ return false;
+ }
+ return Arrays.binarySearch(tmp, o) >= 0;
+ }
+
+ public boolean removeAll(Collection<?> c) {
+ boolean val = false;
+ for (Object t : c) {
+ val |= remove(t);
+ }
+ return val;
+ }
+ public boolean retainAll(Collection<?> c) {
+ boolean val = false;
+ for (T t : this) {
+ if (!c.contains(t)) {
+ val |= remove(t);
+ }
+ }
+ return val;
+ }
+
+ public boolean remove(Object o) {
+ T[] tmp = data.get();
+
+ if (tmp == null) {
+ return false;
+ }
+ int idx = Arrays.binarySearch(tmp, o);
+ if (idx != -1) {
+ if (tmp.length == 1
+ && !data.compareAndSet(tmp, null)) {
+ return remove(o);
+ }
+ T[] tmp2 = newArray(tmp.length - 1);
+ System.arraycopy(tmp, 0, tmp2, 0, idx);
+ System.arraycopy(tmp, idx + 1, tmp2, idx, tmp.length - 1 - idx);
+ if (!data.compareAndSet(tmp, tmp2)) {
+ return remove(o);
+ }
+ return true;
+ }
+ return false;
+ }
+
+
+ public Object[] toArray() {
+ T[] tmp = data.get();
+ if (tmp == null) {
+ return new Object[0];
+ }
+ T[] tmp2 = newArray(tmp.length);
+ System.arraycopy(tmp, 0, tmp2, 0, tmp.length);
+ return tmp2;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <X> X[] toArray(X[] a) {
+ T[] tmp = data.get();
+ if (tmp == null) {
+ if (a.length != 0) {
+ return (X[])java.lang.reflect.Array.
+ newInstance(a.getClass().getComponentType(), 0);
+ }
+ return a;
+ }
+
+ if (a.length < tmp.length) {
+ a = (X[])java.lang.reflect.Array.
+ newInstance(a.getClass().getComponentType(), tmp.length);
+ }
+ System.arraycopy(tmp, 0, a, 0, tmp.length);
+ if (a.length > tmp.length) {
+ a[tmp.length] = null;
+ }
+ return a;
+ }
+
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object o) {
+ if (!(o instanceof SortedArraySet)) {
+ return false;
+ }
+ SortedArraySet<T> as = (SortedArraySet<T>)o;
+ return Arrays.equals(data.get(), as.data.get());
+ }
+ public String toString() {
+ return Arrays.toString(data.get());
+ }
+ public int hashCode() {
+ return Arrays.hashCode(data.get());
+ }
+
+
+ private class SASIterator<X> implements Iterator<X> {
+ final X[] data;
+ int idx;
+
+ SASIterator(X[] d) {
+ data = d;
+ }
+
+ public boolean hasNext() {
+ return data != null && idx != data.length;
+ }
+
+ public X next() {
+ if (data == null || idx == data.length) {
+ throw new NoSuchElementException();
+ }
+ return data[idx++];
+ }
+
+ @Override
+ public void remove() {
+ if (idx > 0) {
+ SortedArraySet.this.remove((Object)data[idx - 1]);
+ }
+ }
+ }
+
+
+ public Comparator<? super T> comparator() {
+ return null;
+ }
+
+ public T first() {
+ T[] tmp = data.get();
+ if (tmp == null || tmp.length == 0) {
+ return null;
+ }
+ return tmp[0];
+ }
+
+ public T last() {
+ T[] tmp = data.get();
+ if (tmp == null || tmp.length == 0) {
+ return null;
+ }
+ return tmp[tmp.length - 1];
+ }
+
+ public SortedSet<T> headSet(T toElement) {
+ throw new UnsupportedOperationException();
+ }
+
+ public SortedSet<T> subSet(T fromElement, T toElement) {
+ throw new UnsupportedOperationException();
+ }
+
+ public SortedSet<T> tailSet(T fromElement) {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/util/SpringClassUnwrapper.java b/transform/src/patch/java/org/apache/cxf/common/util/SpringClassUnwrapper.java
new file mode 100644
index 0000000..a196eb0
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/util/SpringClassUnwrapper.java
@@ -0,0 +1,111 @@
+/**
+ * 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.common.util;
+
+import org.springframework.aop.TargetSource;
+import org.springframework.aop.framework.Advised;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.util.ClassUtils;
+
+/**
+ *
+ */
+class SpringClassUnwrapper implements ClassUnwrapper {
+ SpringClassUnwrapper() throws ClassNotFoundException {
+ Class.forName("org.springframework.aop.support.AopUtils");
+ Class.forName("org.springframework.aop.framework.Advised");
+ }
+
+ @Override
+ public Class<?> getRealClassFromClass(Class<?> cls) {
+ if (ClassUtils.isCglibProxyClass(cls)) {
+ final Class<?> superclass = cls.getSuperclass();
+ // Lambda's generated class names also contain $$ which makes them trigger CGLIB
+ // proxy path. Adding more checks to handle this particular case.
+ if (superclass != null && (superclass != Object.class || wasCglibEnhanced(cls))) {
+ return getRealClassFromClass(superclass);
+ }
+ }
+ return cls;
+ }
+
+ @Override
+ public Object getRealObject(Object o) {
+ if (o instanceof Advised) {
+ try {
+
+ Advised advised = (Advised)o;
+ Object target = advised.getTargetSource().getTarget();
+ //could be a proxy of a proxy.....
+ return getRealObject(target);
+ } catch (Exception ex) {
+ // ignore
+ }
+ }
+ return o;
+ }
+
+ @Override
+ public Class<?> getRealClass(Object o) {
+ if (AopUtils.isAopProxy(o) && (o instanceof Advised)) {
+ Advised advised = (Advised)o;
+ try {
+ TargetSource targetSource = advised.getTargetSource();
+
+ final Object target;
+ try {
+ target = targetSource.getTarget();
+ } catch (BeanCreationException ex) {
+ // some scopes such as 'request' may not
+ // be active on the current thread yet
+ return getRealClassFromClass(targetSource.getTargetClass());
+ }
+
+ if (target == null) {
+ Class<?> targetClass = AopUtils.getTargetClass(o);
+ if (targetClass != null) {
+ return getRealClassFromClass(targetClass);
+ }
+ } else {
+ return getRealClass(target);
+ }
+ } catch (Exception ex) {
+ // ignore
+ }
+
+ } else if (ClassUtils.isCglibProxyClass(o.getClass())) {
+ return getRealClassFromClass(AopUtils.getTargetClass(o));
+ }
+
+ return o.getClass();
+ }
+
+ /**
+ * This additional check is not very reliable since CGLIB allows to
+ * supply own NamingPolicy implementations. However, it works with native
+ * CGLIB proxies ("byCGLIB$$") as well as Spring CGLIB proxies (by "BySpringCGLIB$$").
+ * More expensive approach is to use reflection and inspect the class declared methods,
+ * looking for CGLIB-specific ones like CGLIB$BIND_CALLBACKS.
+ */
+ private static boolean wasCglibEnhanced(Class<?> cls) {
+ return cls.getName().contains("CGLIB");
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/util/SpringClasspathScanner.java b/transform/src/patch/java/org/apache/cxf/common/util/SpringClasspathScanner.java
new file mode 100644
index 0000000..9113d77
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/util/SpringClasspathScanner.java
@@ -0,0 +1,202 @@
+/**
+ * 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.common.util;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.util.ClassUtils;
+
+class SpringClasspathScanner extends ClasspathScanner {
+
+ private static final boolean IN_OSGI = isSpringInOsgi();
+
+
+ SpringClasspathScanner() throws Exception {
+ Class.forName("org.springframework.core.io.support.PathMatchingResourcePatternResolver");
+ Class.forName("org.springframework.core.type.classreading.CachingMetadataReaderFactory");
+ }
+ private static boolean isSpringInOsgi() {
+ try {
+ Class.forName("org.springframework.osgi.io.OsgiBundleResourcePatternResolver");
+ Class.forName("org.springframework.osgi.util.BundleDelegatingClassLoader");
+ return true;
+ } catch (Throwable ex) {
+ return false;
+ }
+ }
+
+ @Override
+ protected Map< Class< ? extends Annotation >, Collection< Class< ? > > > findClassesInternal(
+ Collection< String > basePackages,
+ List<Class< ? extends Annotation > > annotations,
+ ClassLoader loader)
+ throws IOException, ClassNotFoundException {
+
+ ResourcePatternResolver resolver = getResolver(loader);
+ MetadataReaderFactory factory = new CachingMetadataReaderFactory(resolver);
+
+ final Map< Class< ? extends Annotation >, Collection< Class< ? > > > classes =
+ new HashMap<>();
+ final Map< Class< ? extends Annotation >, Collection< String > > matchingInterfaces =
+ new HashMap<>();
+ final Map<String, String[]> nonMatchingClasses = new HashMap<>();
+
+ for (Class< ? extends Annotation > annotation: annotations) {
+ classes.put(annotation, new HashSet<>());
+ matchingInterfaces.put(annotation, new HashSet<>());
+ }
+
+ if (basePackages == null || basePackages.isEmpty()) {
+ return classes;
+ }
+
+ for (final String basePackage: basePackages) {
+ final boolean scanAllPackages = basePackage.equals(WILDCARD);
+ final String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ + (scanAllPackages ? "" : ClassUtils.convertClassNameToResourcePath(basePackage))
+ + ALL_CLASS_FILES;
+
+ final Resource[] resources = resolver.getResources(packageSearchPath);
+
+
+ for (final Resource resource: resources) {
+ final MetadataReader reader = factory.getMetadataReader(resource);
+ final AnnotationMetadata metadata = reader.getAnnotationMetadata();
+
+ if (scanAllPackages && shouldSkip(metadata.getClassName())) {
+ continue;
+ }
+
+ for (Class< ? extends Annotation > annotation: annotations) {
+ boolean concreteClass = !metadata.isInterface() && !metadata.isAbstract();
+ if (metadata.isAnnotated(annotation.getName())) {
+ if (concreteClass) {
+ classes.get(annotation).add(loadClass(metadata.getClassName(), loader));
+ } else {
+ matchingInterfaces.get(annotation).add(metadata.getClassName());
+ }
+ } else if (concreteClass && metadata.getInterfaceNames().length > 0) {
+ nonMatchingClasses.put(metadata.getClassName(), metadata.getInterfaceNames());
+ }
+ }
+ }
+ }
+ if (!nonMatchingClasses.isEmpty()) {
+ for (Map.Entry<Class<? extends Annotation>, Collection<String>> e1 : matchingInterfaces.entrySet()) {
+ for (Map.Entry<String, String[]> e2 : nonMatchingClasses.entrySet()) {
+ for (String intName : e2.getValue()) {
+ if (e1.getValue().contains(intName)) {
+ classes.get(e1.getKey()).add(loadClass(e2.getKey(), loader));
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ for (Map.Entry<Class<? extends Annotation>, Collection<String>> e : matchingInterfaces.entrySet()) {
+ if (classes.get(e.getKey()).isEmpty()) {
+ for (String intName : e.getValue()) {
+ classes.get(e.getKey()).add(loadClass(intName, loader));
+ }
+ }
+ }
+
+ return classes;
+ }
+
+ @Override
+ protected List<URL> findResourcesInternal(Collection<String> basePackages,
+ String extension,
+ ClassLoader loader)
+ throws IOException {
+ final List<URL> resourceURLs = new ArrayList<>();
+ if (basePackages == null || basePackages.isEmpty()) {
+ return resourceURLs;
+ }
+ ResourcePatternResolver resolver = getResolver(loader);
+
+ for (final String basePackage: basePackages) {
+ final boolean scanAllPackages = basePackage.equals(WILDCARD);
+
+ String theBasePackage = basePackage;
+ if (theBasePackage.startsWith(CLASSPATH_URL_SCHEME)) {
+ theBasePackage = theBasePackage.substring(CLASSPATH_URL_SCHEME.length());
+ }
+
+ final String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ + (scanAllPackages ? "" : basePackage.contains(WILDCARD) ? basePackage
+ : ClassUtils.convertClassNameToResourcePath(theBasePackage)) + ALL_FILES + "." + extension;
+
+ final Resource[] resources = resolver.getResources(packageSearchPath);
+ for (final Resource resource: resources) {
+ resourceURLs.add(resource.getURL());
+ }
+ }
+
+ return resourceURLs;
+ }
+
+ private ResourcePatternResolver getResolver(ClassLoader loader) {
+ ResourcePatternResolver resolver = null;
+
+ if (IN_OSGI) {
+ resolver = SpringOsgiUtil.getResolver(loader);
+ }
+ if (resolver == null) {
+ resolver = loader != null
+ ? new PathMatchingResourcePatternResolver(loader) : new PathMatchingResourcePatternResolver();
+ }
+ return resolver;
+ }
+
+ private boolean shouldSkip(final String classname) {
+ for (String packageToSkip: PACKAGES_TO_SKIP) {
+ if (classname.startsWith(packageToSkip)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private Class<?> loadClass(String className, ClassLoader loader)
+ throws ClassNotFoundException {
+ if (loader == null) {
+ return ClassLoaderUtils.loadClass(className, getClass());
+ }
+ return loader.loadClass(className);
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/util/StreamPrinter.java b/transform/src/patch/java/org/apache/cxf/common/util/StreamPrinter.java
new file mode 100644
index 0000000..fa88287
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/util/StreamPrinter.java
@@ -0,0 +1,63 @@
+/**
+ * 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.common.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+class StreamPrinter extends Thread {
+ private InputStream is;
+ private String msg;
+ private OutputStream os;
+
+ StreamPrinter(InputStream stream, String type, OutputStream redirect) {
+ is = stream;
+ msg = type;
+ os = redirect;
+ }
+
+ @Override
+ public void run() {
+ try {
+ PrintWriter pw = null;
+ if (os != null) {
+ pw = new PrintWriter(os);
+ }
+ InputStreamReader isr = new InputStreamReader(is);
+ BufferedReader br = new BufferedReader(isr);
+ String line = br.readLine();
+ while (line != null) {
+ if (pw != null) {
+ pw.println(msg + " " + line);
+ }
+ line = br.readLine();
+ }
+ if (pw != null) {
+ pw.flush();
+ }
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/util/URIParserUtil.java b/transform/src/patch/java/org/apache/cxf/common/util/URIParserUtil.java
new file mode 100644
index 0000000..462896f
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/util/URIParserUtil.java
@@ -0,0 +1,209 @@
+/**
+ * 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.common.util;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.StringTokenizer;
+
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+
+public final class URIParserUtil {
+ private static final String EXCLUDED_CHARS = "<>\"{}|\\^`";
+
+ private URIParserUtil() {
+ // complete
+ }
+
+ private static boolean isExcluded(char ch) {
+ return ch <= 0x20 || ch >= 0x7F || EXCLUDED_CHARS.indexOf(ch) != -1;
+ }
+
+ public static URL[] pathToURLs(String path) {
+ StringTokenizer st = new StringTokenizer(path, File.pathSeparator);
+ URL[] urls = new URL[st.countTokens()];
+ int count = 0;
+ while (st.hasMoreTokens()) {
+ File file = new File(st.nextToken());
+ URL url = null;
+ try {
+ url = file.toURI().toURL();
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+ if (url != null) {
+ urls[count++] = url;
+ }
+ }
+ if (urls.length != count) {
+ URL[] tmp = new URL[count];
+ System.arraycopy(urls, 0, tmp, 0, count);
+ urls = tmp;
+ }
+ return urls;
+ }
+
+ public static String escapeChars(String s) {
+ StringBuilder sb = new StringBuilder(s.length());
+
+ for (int x = 0; x < s.length(); x++) {
+ char ch = s.charAt(x);
+ if (isExcluded(ch)) {
+ byte[] bytes = Character.toString(ch).getBytes(StandardCharsets.UTF_8);
+ for (byte b : bytes) {
+ sb.append('%');
+ StringUtils.byteToHex(b, sb);
+ }
+ } else {
+ sb.append(ch);
+ }
+ }
+ return sb.toString();
+ }
+ public static String normalize(final String uri) {
+ URL url = null;
+ String result;
+ try {
+ url = new URL(uri);
+ result = escapeChars(url.toURI().normalize().toString().replace('\\', '/'));
+ } catch (MalformedURLException e1) {
+ try {
+ if (uri.startsWith("classpath:")) {
+ url = ClassLoaderUtils.getResource(uri.substring(10), URIParserUtil.class);
+ return url != null ? url.toExternalForm() : uri;
+ }
+ File file = new File(uri);
+ if (file.exists()) {
+ return file.toURI().normalize().toString();
+ }
+ final String f;
+ if (uri.indexOf(':') != -1 && !uri.startsWith("/")) {
+ f = "file:/" + uri;
+ } else {
+ f = "file:" + uri;
+ }
+ url = new URL(f);
+ return escapeChars(url.toString().replace("\\", "/"));
+ } catch (Exception e2) {
+ return escapeChars(uri.replace("\\", "/"));
+ }
+ } catch (URISyntaxException e) {
+ result = escapeChars(url.toString().replace("\\", "/"));
+ }
+ return result;
+ }
+
+ public static String getAbsoluteURI(final String arg) {
+ if (arg == null) {
+ return null;
+ }
+
+ try {
+ URI uri = new URI(arg);
+ if ("file".equalsIgnoreCase(uri.getScheme())) {
+ if (!uri.isOpaque()) {
+ return uri.normalize().toString();
+ }
+ return new File("").toURI().resolve(uri.getPath()).toString();
+ }
+ return normalize(arg);
+ } catch (Exception e2) {
+ return normalize(arg);
+ }
+ }
+
+ public static String relativize(String base, String toBeRelativized) throws URISyntaxException {
+ if (base == null || toBeRelativized == null) {
+ return null;
+ }
+ return relativize(new URI(base), new URI(toBeRelativized));
+ }
+
+ /**
+ * This is a custom implementation for doing what URI.relativize(URI uri) should be
+ * doing but is not actually doing when URI roots do not fully match.
+ * See http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6226081
+ *
+ * @param baseURI The base URI
+ * @param toBeRelativizedURI The URI to be relativized
+ * @return The string value of the URI you'd expect to get as result
+ * of calling baseURI.relativize(toBeRelativizedURI).
+ * null is returned if the parameters are null or are not
+ * both absolute or not absolute.
+ * @throws URISyntaxException
+ */
+ public static String relativize(URI baseURI, URI toBeRelativizedURI) throws URISyntaxException {
+ if (baseURI == null || toBeRelativizedURI == null) {
+ return null;
+ }
+ if (baseURI.isAbsolute() ^ toBeRelativizedURI.isAbsolute()) {
+ return null;
+ }
+ final String base = baseURI.getSchemeSpecificPart();
+ final String toBeRelativized = toBeRelativizedURI.getSchemeSpecificPart();
+ final int l1 = base.length();
+ final int l2 = toBeRelativized.length();
+ if (l1 == 0) {
+ return toBeRelativized;
+ }
+ int slashes = 0;
+ StringBuilder sb = new StringBuilder();
+ boolean differenceFound = false;
+ for (int i = 0; i < l1; i++) {
+ char c = base.charAt(i);
+ if (i < l2) {
+ if (!differenceFound && c == toBeRelativized.charAt(i)) {
+ sb.append(c);
+ } else {
+ differenceFound = true;
+ if (c == '/') {
+ slashes++;
+ }
+ }
+ } else {
+ if (c == '/') {
+ slashes++;
+ }
+ }
+ }
+ String rResolved = new URI(getRoot(sb.toString())).relativize(new URI(toBeRelativized)).toString();
+ StringBuilder relativizedPath = new StringBuilder();
+ for (int i = 0; i < slashes; i++) {
+ relativizedPath.append("../");
+ }
+ relativizedPath.append(rResolved);
+ return relativizedPath.toString();
+ }
+
+ private static String getRoot(String uri) {
+ int idx = uri.lastIndexOf('/');
+ if (idx == uri.length() - 1) {
+ return uri;
+ } else if (idx == -1) {
+ return "";
+ } else {
+ return uri.substring(0, idx + 1);
+ }
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/common/xmlschema/SchemaCollection.java b/transform/src/patch/java/org/apache/cxf/common/xmlschema/SchemaCollection.java
new file mode 100644
index 0000000..0f3a579
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/common/xmlschema/SchemaCollection.java
@@ -0,0 +1,389 @@
+/**
+ * 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.common.xmlschema;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.namespace.QName;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.apache.ws.commons.schema.XmlSchema;
+import org.apache.ws.commons.schema.XmlSchemaAll;
+import org.apache.ws.commons.schema.XmlSchemaAttribute;
+import org.apache.ws.commons.schema.XmlSchemaAttributeGroupRef;
+import org.apache.ws.commons.schema.XmlSchemaAttributeOrGroupRef;
+import org.apache.ws.commons.schema.XmlSchemaChoice;
+import org.apache.ws.commons.schema.XmlSchemaCollection;
+import org.apache.ws.commons.schema.XmlSchemaComplexContentExtension;
+import org.apache.ws.commons.schema.XmlSchemaComplexContentRestriction;
+import org.apache.ws.commons.schema.XmlSchemaComplexType;
+import org.apache.ws.commons.schema.XmlSchemaContent;
+import org.apache.ws.commons.schema.XmlSchemaContentModel;
+import org.apache.ws.commons.schema.XmlSchemaElement;
+import org.apache.ws.commons.schema.XmlSchemaParticle;
+import org.apache.ws.commons.schema.XmlSchemaSequence;
+import org.apache.ws.commons.schema.XmlSchemaSequenceMember;
+import org.apache.ws.commons.schema.XmlSchemaSimpleContentExtension;
+import org.apache.ws.commons.schema.XmlSchemaSimpleContentRestriction;
+import org.apache.ws.commons.schema.XmlSchemaType;
+import org.apache.ws.commons.schema.extensions.ExtensionRegistry;
+import org.apache.ws.commons.schema.resolver.URIResolver;
+import org.apache.ws.commons.schema.utils.NamespaceMap;
+import org.apache.ws.commons.schema.utils.NamespacePrefixList;
+import org.apache.ws.commons.schema.utils.XmlSchemaObjectBase;
+
+/**
+ * Wrapper class for XmlSchemaCollection that deals with various quirks and bugs.
+ */
+public class SchemaCollection {
+
+ private final XmlSchemaCollection xmlSchemaCollection;
+ private final Map<XmlSchema, Set<XmlSchemaType>> xmlTypesCheckedForCrossImportsPerSchema
+ = new HashMap<>();
+
+ public SchemaCollection() {
+ this(new XmlSchemaCollection());
+ }
+
+ public SchemaCollection(XmlSchemaCollection col) {
+ xmlSchemaCollection = col;
+ if (xmlSchemaCollection.getNamespaceContext() == null) {
+ // an empty prefix map avoids extra checks for null.
+ xmlSchemaCollection.setNamespaceContext(new NamespaceMap());
+ }
+ }
+
+ public XmlSchemaCollection getXmlSchemaCollection() {
+ return xmlSchemaCollection;
+ }
+
+ public boolean equals(Object obj) {
+ if (obj instanceof SchemaCollection) {
+ return xmlSchemaCollection.equals(((SchemaCollection)obj).xmlSchemaCollection);
+ } else if (obj instanceof XmlSchemaCollection) {
+ return xmlSchemaCollection.equals(obj);
+ }
+ return false;
+ }
+
+ public XmlSchemaElement getElementByQName(QName qname) {
+ return xmlSchemaCollection.getElementByQName(qname);
+ }
+
+ public XmlSchemaAttribute getAttributeByQName(QName qname) {
+ return xmlSchemaCollection.getAttributeByQName(qname);
+ }
+
+ public ExtensionRegistry getExtReg() {
+ return xmlSchemaCollection.getExtReg();
+ }
+
+ public NamespacePrefixList getNamespaceContext() {
+ return xmlSchemaCollection.getNamespaceContext();
+ }
+
+ public XmlSchemaType getTypeByQName(QName schemaTypeName) {
+ return xmlSchemaCollection.getTypeByQName(schemaTypeName);
+ }
+
+ public XmlSchema[] getXmlSchema(String systemId) {
+ return xmlSchemaCollection.getXmlSchema(systemId);
+ }
+
+ public XmlSchema[] getXmlSchemas() {
+ return xmlSchemaCollection.getXmlSchemas();
+ }
+
+ public int hashCode() {
+ return xmlSchemaCollection.hashCode();
+ }
+
+ public void init() {
+ xmlSchemaCollection.init();
+ }
+
+ public XmlSchema read(Element elem, String uri) {
+ return xmlSchemaCollection.read(elem, uri);
+ }
+
+ public XmlSchema read(Document d, String uri) {
+ return xmlSchemaCollection.read(d, uri);
+ }
+
+ public XmlSchema read(Element elem) {
+ return xmlSchemaCollection.read(elem);
+ }
+
+ public void setBaseUri(String baseUri) {
+ xmlSchemaCollection.setBaseUri(baseUri);
+ }
+
+ public void setExtReg(ExtensionRegistry extReg) {
+ xmlSchemaCollection.setExtReg(extReg);
+ }
+
+ public void setNamespaceContext(NamespacePrefixList namespaceContext) {
+ xmlSchemaCollection.setNamespaceContext(namespaceContext);
+ }
+
+ public void setSchemaResolver(URIResolver schemaResolver) {
+ xmlSchemaCollection.setSchemaResolver(schemaResolver);
+ }
+
+ /**
+ * This function is not part of the XmlSchema API. Who knows why?
+ *
+ * @param namespaceURI targetNamespace
+ * @return schema, or null.
+ */
+ public XmlSchema getSchemaByTargetNamespace(String namespaceURI) {
+ for (XmlSchema schema : xmlSchemaCollection.getXmlSchemas()) {
+ if (namespaceURI != null && namespaceURI.equals(schema.getTargetNamespace())
+ || namespaceURI == null && schema.getTargetNamespace() == null) {
+ return schema;
+ }
+ }
+ return null;
+ }
+
+ public XmlSchema getSchemaForElement(QName name) {
+ for (XmlSchema schema : xmlSchemaCollection.getXmlSchemas()) {
+ if (name.getNamespaceURI().equals(schema.getTargetNamespace())) {
+
+ if (schema.getElementByName(name.getLocalPart()) != null) {
+ return schema;
+ } else if (schema.getElementByName(name) != null) {
+ return schema;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Once upon a time, XmlSchema had a bug in the constructor used in this function. So this wrapper was
+ * created to hold a workaround.
+ *
+ * @param namespaceURI TNS for new schema.
+ * @return new schema
+ */
+
+ public XmlSchema newXmlSchemaInCollection(String namespaceURI) {
+ return new XmlSchema(namespaceURI, xmlSchemaCollection);
+ }
+
+ /**
+ * Validate that a qualified name points to some namespace in the schema.
+ *
+ * @param qname
+ */
+ public void validateQNameNamespace(QName qname) {
+ // astonishingly, xmlSchemaCollection has no accessor by target URL.
+ if ("".equals(qname.getNamespaceURI())) {
+ return; // references to the 'unqualified' namespace are OK even if there is no schema for it.
+ }
+ for (XmlSchema schema : xmlSchemaCollection.getXmlSchemas()) {
+ if (schema.getTargetNamespace().equals(qname.getNamespaceURI())) {
+ return;
+ }
+ }
+ throw new InvalidXmlSchemaReferenceException(qname + " refers to unknown namespace.");
+ }
+
+ public void validateElementName(QName referrer, QName elementQName) {
+ XmlSchemaElement element = xmlSchemaCollection.getElementByQName(elementQName);
+ if (element == null) {
+ throw new InvalidXmlSchemaReferenceException(referrer + " references non-existent element "
+ + elementQName);
+ }
+ }
+
+ public void validateTypeName(QName referrer, QName typeQName) {
+ XmlSchemaType type = xmlSchemaCollection.getTypeByQName(typeQName);
+ if (type == null) {
+ throw new InvalidXmlSchemaReferenceException(referrer + " references non-existent type "
+ + typeQName);
+ }
+ }
+
+ public void addCrossImports() {
+ /*
+ * We need to inventory all the cross-imports to see if any are missing.
+ */
+ for (XmlSchema schema : xmlSchemaCollection.getXmlSchemas()) {
+ addOneSchemaCrossImports(schema);
+ }
+ }
+
+ private void addOneSchemaCrossImports(XmlSchema schema) {
+ /*
+ * We need to visit all the top-level items.
+ */
+ for (XmlSchemaElement element : schema.getElements().values()) {
+ addElementCrossImportsElement(schema, element);
+ }
+ for (XmlSchemaAttribute attribute : schema.getAttributes().values()) {
+ XmlSchemaUtils.addImportIfNeeded(schema, attribute.getRef().getTargetQName());
+ XmlSchemaUtils.addImportIfNeeded(schema, attribute.getSchemaTypeName());
+ }
+ for (XmlSchemaType type : schema.getSchemaTypes().values()) {
+ addCrossImportsType(schema, type);
+ }
+ }
+
+ private void addElementCrossImportsElement(XmlSchema schema, XmlSchemaElement item) {
+ XmlSchemaElement element = item;
+ XmlSchemaUtils.addImportIfNeeded(schema, element.getRef().getTargetQName());
+ XmlSchemaUtils.addImportIfNeeded(schema, element.getSchemaTypeName());
+ // if there's an anonymous type, it might have element refs in it.
+ XmlSchemaType schemaType = element.getSchemaType();
+ if (!crossImportsAdded(schema, schemaType)) {
+ addCrossImportsType(schema, schemaType);
+ }
+ }
+
+ /**
+ * Determines whether the schema has already received (cross) imports for the schemaType
+ *
+ * @param schema
+ * @param schemaType
+ * @return false if cross imports for schemaType must still be added to schema
+ */
+ private boolean crossImportsAdded(XmlSchema schema, XmlSchemaType schemaType) {
+ boolean result = true;
+ if (schemaType != null) {
+ Set<XmlSchemaType> xmlTypesCheckedForCrossImports;
+ if (!xmlTypesCheckedForCrossImportsPerSchema.containsKey(schema)) {
+ xmlTypesCheckedForCrossImports = new HashSet<>();
+ xmlTypesCheckedForCrossImportsPerSchema.put(schema, xmlTypesCheckedForCrossImports);
+ } else {
+ xmlTypesCheckedForCrossImports = xmlTypesCheckedForCrossImportsPerSchema.get(schema);
+ }
+ if (!xmlTypesCheckedForCrossImports.contains(schemaType)) {
+ // cross imports for this schemaType have not yet been added
+ xmlTypesCheckedForCrossImports.add(schemaType);
+ result = false;
+ }
+ }
+ return result;
+ }
+
+ private void addCrossImportsType(XmlSchema schema, XmlSchemaType schemaType) {
+ // the base type might cross schemas.
+ if (schemaType instanceof XmlSchemaComplexType) {
+ XmlSchemaComplexType complexType = (XmlSchemaComplexType)schemaType;
+ XmlSchemaUtils.addImportIfNeeded(schema, complexType.getBaseSchemaTypeName());
+ addCrossImports(schema, complexType.getContentModel());
+ addCrossImportsAttributeList(schema, complexType.getAttributes());
+ // could it be a choice or something else?
+
+ if (complexType.getParticle() instanceof XmlSchemaChoice) {
+ XmlSchemaChoice choice = (XmlSchemaChoice)complexType.getParticle();
+ addCrossImports(schema, choice);
+ } else if (complexType.getParticle() instanceof XmlSchemaAll) {
+ XmlSchemaAll all = (XmlSchemaAll)complexType.getParticle();
+ addCrossImports(schema, all);
+ } else if (complexType.getParticle() instanceof XmlSchemaSequence) {
+ XmlSchemaSequence sequence = (XmlSchemaSequence)complexType.getParticle();
+ addCrossImports(schema, sequence);
+ }
+ }
+ }
+ private void addCrossImports(XmlSchema schema, XmlSchemaAll all) {
+ for (XmlSchemaObjectBase seqMember : all.getItems()) {
+ if (seqMember instanceof XmlSchemaElement) {
+ addElementCrossImportsElement(schema, (XmlSchemaElement)seqMember);
+ }
+ }
+ }
+
+ private void addCrossImports(XmlSchema schema, XmlSchemaChoice choice) {
+ for (XmlSchemaObjectBase seqMember : choice.getItems()) {
+ if (seqMember instanceof XmlSchemaElement) {
+ addElementCrossImportsElement(schema, (XmlSchemaElement)seqMember);
+ }
+ }
+ }
+ private void addCrossImports(XmlSchema schema, XmlSchemaSequence sequence) {
+ for (XmlSchemaSequenceMember seqMember : sequence.getItems()) {
+ if (seqMember instanceof XmlSchemaElement) {
+ addElementCrossImportsElement(schema, (XmlSchemaElement)seqMember);
+ }
+ }
+ }
+
+ private void addCrossImportsAttributeList(XmlSchema schema, List<XmlSchemaAttributeOrGroupRef> list) {
+ for (XmlSchemaAttributeOrGroupRef attr : list) {
+ final QName ref;
+ if (attr instanceof XmlSchemaAttribute) {
+ ref = ((XmlSchemaAttribute)attr).getRef().getTargetQName();
+ } else {
+ XmlSchemaAttributeGroupRef groupRef = (XmlSchemaAttributeGroupRef)attr;
+ ref = groupRef.getRef().getTargetQName();
+ }
+
+ if (ref != null) {
+ XmlSchemaUtils.addImportIfNeeded(schema, ref);
+ }
+ }
+ }
+
+ private void addCrossImports(XmlSchema schema, XmlSchemaContentModel contentModel) {
+ if (contentModel == null) {
+ return;
+ }
+ XmlSchemaContent content = contentModel.getContent();
+ if (content == null) {
+ return;
+ }
+ if (content instanceof XmlSchemaComplexContentExtension) {
+ XmlSchemaComplexContentExtension extension = (XmlSchemaComplexContentExtension)content;
+ XmlSchemaUtils.addImportIfNeeded(schema, extension.getBaseTypeName());
+ addCrossImportsAttributeList(schema, extension.getAttributes());
+ XmlSchemaParticle particle = extension.getParticle();
+ if (particle instanceof XmlSchemaSequence) {
+ addCrossImports(schema, (XmlSchemaSequence)particle);
+ } else if (particle instanceof XmlSchemaChoice) {
+ addCrossImports(schema, (XmlSchemaChoice)particle);
+ } else if (particle instanceof XmlSchemaAll) {
+ addCrossImports(schema, (XmlSchemaAll)particle);
+ }
+ } else if (content instanceof XmlSchemaComplexContentRestriction) {
+ XmlSchemaComplexContentRestriction restriction = (XmlSchemaComplexContentRestriction)content;
+ XmlSchemaUtils.addImportIfNeeded(schema, restriction.getBaseTypeName());
+ addCrossImportsAttributeList(schema, restriction.getAttributes());
+ } else if (content instanceof XmlSchemaSimpleContentExtension) {
+ XmlSchemaSimpleContentExtension extension = (XmlSchemaSimpleContentExtension)content;
+ XmlSchemaUtils.addImportIfNeeded(schema, extension.getBaseTypeName());
+ addCrossImportsAttributeList(schema, extension.getAttributes());
+ } else if (content instanceof XmlSchemaSimpleContentRestriction) {
+ XmlSchemaSimpleContentRestriction restriction = (XmlSchemaSimpleContentRestriction)content;
+ XmlSchemaUtils.addImportIfNeeded(schema, restriction.getBaseTypeName());
+ addCrossImportsAttributeList(schema, restriction.getAttributes());
+ }
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/configuration/jsse/MultiKeyPasswordKeyManager.java b/transform/src/patch/java/org/apache/cxf/configuration/jsse/MultiKeyPasswordKeyManager.java
new file mode 100644
index 0000000..6a1aee5
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/configuration/jsse/MultiKeyPasswordKeyManager.java
@@ -0,0 +1,84 @@
+/**
+ * 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.configuration.jsse;
+
+import java.net.Socket;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.X509KeyManager;
+
+public class MultiKeyPasswordKeyManager implements X509KeyManager {
+ private final KeyStore mKeyStore;
+ private final String mKeyAlias;
+ private final String mKeyPassword;
+
+ public MultiKeyPasswordKeyManager(KeyStore keystore, String keyAlias, String keyPassword) {
+ mKeyStore = keystore;
+ mKeyAlias = keyAlias;
+ mKeyPassword = keyPassword;
+ }
+
+ public String[] getClientAliases(String keyType, Principal[] issuers) {
+ return new String[] {
+ mKeyAlias
+ };
+ }
+
+ public String[] getServerAliases(String keyType, Principal[] issuers) {
+ return new String[] {
+ mKeyAlias
+ };
+ }
+ public X509Certificate[] getCertificateChain(String alias) {
+ final Certificate[] chain;
+ try {
+ chain = mKeyStore.getCertificateChain(alias);
+ } catch (KeyStoreException kse) {
+ throw new RuntimeException(kse);
+ }
+ final X509Certificate[] certChain = new X509Certificate[chain.length];
+ for (int i = 0; i < chain.length; i++) {
+ certChain[i] = (X509Certificate)chain[i];
+ }
+ return certChain;
+ }
+
+ public PrivateKey getPrivateKey(String alias) {
+ try {
+ return (PrivateKey)mKeyStore.getKey(alias, mKeyPassword.toCharArray());
+ } catch (GeneralSecurityException gse) {
+ throw new RuntimeException(gse);
+ }
+ }
+
+ public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
+ return mKeyAlias;
+ }
+
+ public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
+ return mKeyAlias;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/configuration/jsse/TLSClientParameters.java b/transform/src/patch/java/org/apache/cxf/configuration/jsse/TLSClientParameters.java
new file mode 100644
index 0000000..3e0099a
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/configuration/jsse/TLSClientParameters.java
@@ -0,0 +1,258 @@
+/**
+ * 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.configuration.jsse;
+
+import java.util.List;
+import java.util.Objects;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * This class extends {@link TLSParameterBase} with client-specific
+ * SSL/TLS parameters.
+ *
+ */
+public class TLSClientParameters extends TLSParameterBase {
+ private boolean disableCNCheck;
+ private SSLSocketFactory sslSocketFactory;
+ private int sslCacheTimeout = 86400;
+ private boolean useHttpsURLConnectionDefaultSslSocketFactory;
+ private boolean useHttpsURLConnectionDefaultHostnameVerifier;
+ private HostnameVerifier hostnameVerifier;
+ private SSLContext sslContext;
+
+ /**
+ * Set custom HostnameVerifier
+ * @param verifier hostname verifier
+ */
+ public void setHostnameVerifier(HostnameVerifier verifier) {
+ hostnameVerifier = verifier;
+ }
+
+ /**
+ * Get custom HostnameVerifier
+ * @return hostname verifier
+ */
+ public HostnameVerifier getHostnameVerifier() {
+ return hostnameVerifier;
+ }
+ /**
+ * Set whether or not JSEE should omit checking if the host name
+ * specified in the URL matches that of the Common Name
+ * (CN) on the server's certificate. Default is false;
+ * this attribute should not be set to true during production use.
+ */
+ public void setDisableCNCheck(boolean disableCNCheck) {
+ this.disableCNCheck = disableCNCheck;
+ }
+
+ /**
+ * Returns whether or not JSSE omits checking if the
+ * host name specified in the URL matches that of the Common Name
+ * (CN) on the server's certificate.
+ */
+ public boolean isDisableCNCheck() {
+ return disableCNCheck;
+ }
+
+ /**
+ * This sets the SSLSocketFactory to use, causing all other properties of
+ * this bean (and its superclass) to get ignored (this takes precendence).
+ */
+ public final void setSSLSocketFactory(SSLSocketFactory factory) {
+ sslSocketFactory = factory;
+ }
+
+
+ /**
+ * Returns the SSLSocketFactory to be used, or null if none has been set.
+ */
+ public final SSLSocketFactory getSSLSocketFactory() {
+ return sslSocketFactory;
+ }
+
+ /**
+ * Returns the SSL cache timeout in seconds if it has been configured or the default value
+ */
+ public int getSslCacheTimeout() {
+ return sslCacheTimeout;
+ }
+
+ /**
+ * This sets the SSL Session Cache timeout value in seconds for client sessions handled by CXF
+ */
+ public void setSslCacheTimeout(int sslCacheTimeout) {
+ this.sslCacheTimeout = sslCacheTimeout;
+ }
+
+
+ /**
+ * Returns whether or not {@link javax.net.ssl.HttpsURLConnection#getDefaultSSLSocketFactory()} should be
+ * used to create https connections. If <code>true</code> , {@link #getJsseProvider()} ,
+ * {@link #getSecureSocketProtocol()}, {@link #getTrustManagers()}, {@link #getKeyManagers()},
+ * {@link #getSecureRandom()}, {@link #getCipherSuites()} and {@link #getCipherSuitesFilter()} are
+ * ignored.
+ */
+ public boolean isUseHttpsURLConnectionDefaultSslSocketFactory() {
+ return useHttpsURLConnectionDefaultSslSocketFactory;
+ }
+
+ /**
+ * Sets whether or not {@link javax.net.ssl.HttpsURLConnection#getDefaultSSLSocketFactory()} should be
+ * used to create https connections.
+ *
+ * @see #isUseHttpsURLConnectionDefaultSslSocketFactory()
+ */
+ public void setUseHttpsURLConnectionDefaultSslSocketFactory(
+ boolean useHttpsURLConnectionDefaultSslSocketFactory) {
+ this.useHttpsURLConnectionDefaultSslSocketFactory = useHttpsURLConnectionDefaultSslSocketFactory;
+ }
+
+ /**
+ * Returns whether or not {@link javax.net.ssl.HttpsURLConnection#getDefaultHostnameVerifier()} should be
+ * used to create https connections. If <code>true</code>, {@link #isDisableCNCheck()} is ignored.
+ */
+ public boolean isUseHttpsURLConnectionDefaultHostnameVerifier() {
+ return useHttpsURLConnectionDefaultHostnameVerifier;
+ }
+
+ /**
+ * Sets whether or not {@link javax.net.ssl.HttpsURLConnection#getDefaultHostnameVerifier()} should be
+ * used to create https connections.
+ *
+ * @see #isUseHttpsURLConnectionDefaultHostnameVerifier()
+ */
+ public void setUseHttpsURLConnectionDefaultHostnameVerifier(
+ boolean useHttpsURLConnectionDefaultHostnameVerifier) {
+ this.useHttpsURLConnectionDefaultHostnameVerifier = useHttpsURLConnectionDefaultHostnameVerifier;
+ }
+
+ public int hashCode() {
+ int hash = disableCNCheck ? 37 : 17;
+ if (sslSocketFactory != null) {
+ hash = hash * 41 + System.identityHashCode(sslSocketFactory);
+ }
+ if (sslContext != null) {
+ hash = hash * 41 + System.identityHashCode(sslContext);
+ }
+ hash = hash(hash, useHttpsURLConnectionDefaultSslSocketFactory);
+ hash = hash(hash, useHttpsURLConnectionDefaultHostnameVerifier);
+ hash = hash(hash, sslCacheTimeout);
+ hash = hash(hash, secureRandom);
+ hash = hash(hash, protocol);
+ hash = hash(hash, certAlias);
+ hash = hash(hash, provider);
+ for (String cs : ciphersuites) {
+ hash = hash(hash, cs);
+ }
+ hash = hash(hash, keyManagers);
+ hash = hash(hash, trustManagers);
+ if (cipherSuiteFilters != null) {
+ hash = hash(hash, cipherSuiteFilters.getInclude());
+ hash = hash(hash, cipherSuiteFilters.getExclude());
+ }
+ if (certConstraints != null) {
+ hash = hash(hash, certConstraints.getIssuerDNConstraints());
+ hash = hash(hash, certConstraints.getSubjectDNConstraints());
+ }
+ return hash;
+ }
+ private int hash(int i, Object o) {
+ if (o != null) {
+ return i * 37 + o.hashCode();
+ }
+ return i;
+ }
+ private int hash(int i, Object[] os) {
+ if (os == null) {
+ return i;
+ }
+ for (Object o : os) {
+ i = hash(i, o);
+ }
+ return i;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o instanceof TLSClientParameters) {
+ TLSClientParameters that = (TLSClientParameters)o;
+ boolean eq = disableCNCheck == that.disableCNCheck;
+ eq &= sslSocketFactory == that.sslSocketFactory;
+ eq &= sslContext == that.sslContext;
+ eq &= useHttpsURLConnectionDefaultSslSocketFactory == that.useHttpsURLConnectionDefaultSslSocketFactory;
+ eq &= useHttpsURLConnectionDefaultHostnameVerifier == that.useHttpsURLConnectionDefaultHostnameVerifier;
+ eq &= sslCacheTimeout == that.sslCacheTimeout;
+ eq &= secureRandom == that.secureRandom;
+ eq &= Objects.equals(certAlias, that.certAlias);
+ eq &= Objects.equals(protocol, that.protocol);
+ eq &= Objects.equals(provider, that.provider);
+ eq &= equals(ciphersuites, that.ciphersuites);
+ eq &= Objects.deepEquals(keyManagers, that.keyManagers);
+ eq &= Objects.deepEquals(trustManagers, that.trustManagers);
+ if (cipherSuiteFilters != null) {
+ if (that.cipherSuiteFilters != null) {
+ eq &= equals(cipherSuiteFilters.getExclude(), that.cipherSuiteFilters.getExclude());
+ eq &= equals(cipherSuiteFilters.getInclude(), that.cipherSuiteFilters.getInclude());
+ } else {
+ eq = false;
+ }
+ } else {
+ eq &= that.cipherSuiteFilters == null;
+ }
+ if (certConstraints != null) {
+ if (that.certConstraints != null) {
+ eq &= Objects.equals(certConstraints.getIssuerDNConstraints(),
+ that.certConstraints.getIssuerDNConstraints());
+ eq &= Objects.equals(certConstraints.getSubjectDNConstraints(),
+ that.certConstraints.getSubjectDNConstraints());
+ } else {
+ eq = false;
+ }
+ } else {
+ eq &= that.certConstraints == null;
+ }
+ return eq;
+ }
+ return false;
+ }
+
+ private static boolean equals(final List<?> obj1, final List<?> obj2) {
+ return obj1.equals(obj2);
+ }
+
+ /**
+ * Get the SSLContext parameter to use (if it has been set)
+ */
+ public SSLContext getSslContext() {
+ return sslContext;
+ }
+
+ /**
+ * Set an SSLContext parameter to use to create https connections
+ */
+ public void setSslContext(SSLContext sslContext) {
+ this.sslContext = sslContext;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/configuration/jsse/TLSParameterJaxBUtils.java b/transform/src/patch/java/org/apache/cxf/configuration/jsse/TLSParameterJaxBUtils.java
new file mode 100644
index 0000000..bd16aa4
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/configuration/jsse/TLSParameterJaxBUtils.java
@@ -0,0 +1,420 @@
+/**
+ * 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.configuration.jsse;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.X509CertSelector;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.CertPathTrustManagerParameters;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.PasswordCallback;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.configuration.security.CertStoreType;
+import org.apache.cxf.configuration.security.KeyManagersType;
+import org.apache.cxf.configuration.security.KeyStoreType;
+import org.apache.cxf.configuration.security.SecureRandomParameters;
+import org.apache.cxf.configuration.security.TrustManagersType;
+import org.apache.cxf.resource.ResourceManager;
+
+/**
+ * This class provides some functionality to convert the JAXB
+ * generated types in the security.xsd to the items needed
+ * to programatically configure the HTTPConduit and HTTPDestination
+ * with TLSClientParameters and TLSServerParameters respectively.
+ */
+public final class TLSParameterJaxBUtils {
+
+ private static final Logger LOG =
+ LogUtils.getL7dLogger(TLSParameterJaxBUtils.class);
+
+ private TLSParameterJaxBUtils() {
+ // empty
+ }
+ /**
+ * This method converts the JAXB generated type into a SecureRandom.
+ */
+ public static SecureRandom getSecureRandom(
+ SecureRandomParameters secureRandomParams
+ ) throws GeneralSecurityException {
+
+ SecureRandom secureRandom = null;
+ if (secureRandomParams != null) {
+ String secureRandomAlg =
+ secureRandomParams.getAlgorithm();
+ String randomProvider =
+ secureRandomParams.getProvider();
+ if (randomProvider != null) {
+ secureRandom = secureRandomAlg != null
+ ? SecureRandom.getInstance(
+ secureRandomAlg,
+ randomProvider)
+ : null;
+ } else {
+ secureRandom = secureRandomAlg != null
+ ? SecureRandom.getInstance(
+ secureRandomAlg)
+ : null;
+ }
+ }
+ return secureRandom;
+ }
+
+ public static KeyStore getKeyStore(KeyStoreType kst) throws GeneralSecurityException, IOException {
+ return getKeyStore(kst, false);
+ }
+
+ /**
+ * This method converts a JAXB generated KeyStoreType into a KeyStore.
+ */
+ public static KeyStore getKeyStore(KeyStoreType kst, boolean trustStore)
+ throws GeneralSecurityException,
+ IOException {
+
+ if (kst == null) {
+ return null;
+ }
+ final String type;
+ if (trustStore) {
+ type = SSLUtils.getTrustStoreType(kst.isSetType()
+ ? kst.getType() : null, LOG, KeyStore.getDefaultType());
+ } else {
+ type = SSLUtils.getKeystoreType(kst.isSetType()
+ ? kst.getType() : null, LOG, KeyStore.getDefaultType());
+ }
+
+ char[] password = kst.isSetPassword()
+ ? deobfuscate(kst.getPassword())
+ : null;
+ if (password == null) {
+ final String tmp;
+ if (trustStore) {
+ tmp = SSLUtils.getTruststorePassword(null, LOG);
+ } else {
+ tmp = SSLUtils.getKeystorePassword(null, LOG);
+ }
+ if (tmp != null) {
+ password = tmp.toCharArray();
+ }
+ }
+ final String provider;
+ if (trustStore) {
+ provider = SSLUtils.getTruststoreProvider(kst.isSetProvider() ? kst.getProvider() : null, LOG);
+ } else {
+ provider = SSLUtils.getKeystoreProvider(kst.isSetProvider() ? kst.getProvider() : null, LOG);
+ }
+ KeyStore keyStore = provider == null
+ ? KeyStore.getInstance(type)
+ : KeyStore.getInstance(type, provider);
+
+ if (kst.isSetFile()) {
+ try (InputStream kstInputStream = Files.newInputStream(Paths.get(kst.getFile()))) {
+ keyStore.load(kstInputStream, password);
+ }
+ } else if (kst.isSetResource()) {
+ final InputStream is = getResourceAsStream(kst.getResource());
+ if (is == null) {
+ final String msg =
+ "Could not load keystore resource " + kst.getResource();
+ LOG.severe(msg);
+ throw new IOException(msg);
+ }
+ keyStore.load(is, password);
+ } else if (kst.isSetUrl()) {
+ keyStore.load(new URL(kst.getUrl()).openStream(), password);
+ } else {
+ final String loc;
+ if (trustStore) {
+ loc = SSLUtils.getTruststore(null, LOG);
+ } else {
+ loc = SSLUtils.getKeystore(null, LOG);
+ }
+ if (loc != null) {
+ try (InputStream ins = Files.newInputStream(Paths.get(loc))) {
+ keyStore.load(ins, password);
+ } catch (NoSuchFileException ex) {
+ // Fall back to load the location as a stream
+ try (InputStream ins = getResourceAsStream(loc)) {
+ keyStore.load(ins, password);
+ }
+ }
+ }
+ }
+ return keyStore;
+ }
+
+ /**
+ * This method converts a JAXB generated CertStoreType into a KeyStore.
+ */
+ public static KeyStore getKeyStore(final CertStoreType pst)
+ throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException {
+
+ if (pst == null) {
+ return null;
+ }
+ String type;
+ if (pst.isSetType()) {
+ type = pst.getType();
+ } else {
+ type = KeyStore.getDefaultType();
+ }
+ if (pst.isSetFile()) {
+ InputStream is = Files.newInputStream(Paths.get(pst.getFile()));
+ return createTrustStore(is, type);
+ }
+ if (pst.isSetResource()) {
+ final InputStream is = getResourceAsStream(pst.getResource());
+ if (is == null) {
+ final String msg =
+ "Could not load truststore resource " + pst.getResource();
+ LOG.severe(msg);
+ throw new IOException(msg);
+ }
+ return createTrustStore(is, type);
+ }
+ if (pst.isSetUrl()) {
+ return createTrustStore(new URL(pst.getUrl()).openStream(), type);
+ }
+ throw new IllegalArgumentException("Could not create KeyStore based on information in CertStoreType");
+ }
+
+ private static InputStream getResourceAsStream(String resource) {
+ InputStream is = ClassLoaderUtils.getResourceAsStream(resource, TLSParameterJaxBUtils.class);
+ if (is == null) {
+ Bus bus = BusFactory.getThreadDefaultBus(true);
+ ResourceManager rm = bus.getExtension(ResourceManager.class);
+ if (rm != null) {
+ is = rm.getResourceAsStream(resource);
+ }
+ }
+ return is;
+ }
+
+ /**
+ * Create a KeyStore containing the trusted CA certificates contained
+ * in the supplied input stream.
+ */
+ private static KeyStore createTrustStore(final InputStream is, String type)
+ throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException {
+
+ final Collection<? extends Certificate> certs = loadCertificates(is);
+ final KeyStore keyStore =
+ KeyStore.getInstance(type);
+ keyStore.load(null, null);
+ for (Certificate cert : certs) {
+ final X509Certificate xcert = (X509Certificate) cert;
+ keyStore.setCertificateEntry(
+ xcert.getSubjectX500Principal().getName(),
+ cert
+ );
+ }
+ return keyStore;
+ }
+
+ /**
+ * load the certificates as X.509 certificates
+ */
+ private static Collection<? extends Certificate> loadCertificates(final InputStream is)
+ throws IOException, CertificateException {
+
+ final CertificateFactory factory = CertificateFactory.getInstance("X.509");
+ return factory.generateCertificates(is);
+ }
+
+ private static char[] deobfuscate(String s) {
+ // From the Jetty org.eclipse.jetty.http.security.Password class
+ if (!s.startsWith("OBF:")) {
+ return s.toCharArray();
+ }
+ s = s.substring(4);
+
+ char[] b = new char[s.length() / 2];
+ int l = 0;
+ for (int i = 0; i < s.length(); i += 4) {
+ String x = s.substring(i, i + 4);
+ int i0 = Integer.parseInt(x, 36);
+ int i1 = i0 / 256;
+ int i2 = i0 % 256;
+ b[l++] = (char) ((i1 + i2 - 254) / 2);
+ }
+
+ return new String(b, 0, l).toCharArray();
+ }
+
+ /**
+ * This method converts the JAXB KeyManagersType into a list of
+ * JSSE KeyManagers.
+ */
+ public static KeyManager[] getKeyManagers(KeyManagersType kmc)
+ throws GeneralSecurityException,
+ IOException {
+
+ KeyStore keyStore = getKeyStore(kmc.getKeyStore(), false);
+
+ String alg = kmc.isSetFactoryAlgorithm()
+ ? kmc.getFactoryAlgorithm()
+ : KeyManagerFactory.getDefaultAlgorithm();
+
+ char[] keyPass = getKeyPassword(kmc);
+
+ KeyManagerFactory fac =
+ kmc.isSetProvider()
+ ? KeyManagerFactory.getInstance(alg, kmc.getProvider())
+ : KeyManagerFactory.getInstance(alg);
+
+ fac.init(keyStore, keyPass);
+
+ return fac.getKeyManagers();
+ }
+
+ /**
+ * This method converts the JAXB KeyManagersType into a list of
+ * JSSE KeyManagers.
+ */
+ public static KeyManager[] getKeyManagers(KeyManagersType kmc, String alias)
+ throws GeneralSecurityException,
+ IOException {
+
+ KeyStore keyStore = getKeyStore(kmc.getKeyStore(), false);
+
+ String alg = kmc.isSetFactoryAlgorithm()
+ ? kmc.getFactoryAlgorithm()
+ : KeyManagerFactory.getDefaultAlgorithm();
+
+ char[] keyPass = getKeyPassword(kmc);
+
+ KeyManagerFactory fac =
+ kmc.isSetProvider()
+ ? KeyManagerFactory.getInstance(alg, kmc.getProvider())
+ : KeyManagerFactory.getInstance(alg);
+
+ try {
+ fac.init(keyStore, keyPass);
+
+ return fac.getKeyManagers();
+ } catch (java.security.UnrecoverableKeyException uke) {
+ //jsse has the restriction that different key in keystore
+ //cannot has different password, use MultiKeyPasswordKeyManager
+ //as fallback when this happen
+ MultiKeyPasswordKeyManager manager
+ = new MultiKeyPasswordKeyManager(keyStore, alias,
+ new String(keyPass));
+ return new KeyManager[]{manager};
+ }
+ }
+
+ private static char[] getKeyPassword(KeyManagersType kmc) {
+ char[] keyPass = kmc.isSetKeyPassword()
+ ? deobfuscate(kmc.getKeyPassword())
+ : null;
+
+ if (keyPass != null) {
+ return keyPass;
+ }
+
+ String callbackHandlerClass = kmc.getKeyPasswordCallbackHandler();
+ if (callbackHandlerClass == null) {
+ return null;
+ }
+ try {
+ final CallbackHandler ch = (CallbackHandler) ClassLoaderUtils
+ .loadClass(callbackHandlerClass, TLSParameterJaxBUtils.class).newInstance();
+ String prompt = kmc.getKeyStore().getFile();
+ if (prompt == null) {
+ prompt = kmc.getKeyStore().getResource();
+ }
+ PasswordCallback pwCb = new PasswordCallback(prompt, false);
+ PasswordCallback[] callbacks = new PasswordCallback[] {pwCb};
+ ch.handle(callbacks);
+ keyPass = callbacks[0].getPassword();
+ } catch (Exception e) {
+ LOG.log(Level.WARNING,
+ "Cannot load key password from callback handler: " + e.getMessage(), e);
+ }
+ return keyPass;
+ }
+
+ /**
+ * This method converts the JAXB TrustManagersType into a list of
+ * JSSE TrustManagers.
+ */
+ @Deprecated
+ public static TrustManager[] getTrustManagers(TrustManagersType tmc)
+ throws GeneralSecurityException,
+ IOException {
+ return getTrustManagers(tmc, false);
+ }
+
+ public static TrustManager[] getTrustManagers(TrustManagersType tmc, boolean enableRevocation)
+ throws GeneralSecurityException,
+ IOException {
+
+ final KeyStore keyStore =
+ tmc.isSetKeyStore()
+ ? getKeyStore(tmc.getKeyStore(), true)
+ : (tmc.isSetCertStore()
+ ? getKeyStore(tmc.getCertStore())
+ : null);
+
+ String alg = tmc.isSetFactoryAlgorithm()
+ ? tmc.getFactoryAlgorithm()
+ : TrustManagerFactory.getDefaultAlgorithm();
+
+ TrustManagerFactory fac =
+ tmc.isSetProvider()
+ ? TrustManagerFactory.getInstance(alg, tmc.getProvider())
+ : TrustManagerFactory.getInstance(alg);
+
+ if (enableRevocation) {
+ PKIXBuilderParameters param = new PKIXBuilderParameters(keyStore, new X509CertSelector());
+ param.setRevocationEnabled(true);
+
+ fac.init(new CertPathTrustManagerParameters(param));
+ } else {
+ fac.init(keyStore);
+ }
+
+ return fac.getTrustManagers();
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/configuration/spring/ConfigurerImpl.java b/transform/src/patch/java/org/apache/cxf/configuration/spring/ConfigurerImpl.java
new file mode 100644
index 0000000..d920936
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/configuration/spring/ConfigurerImpl.java
@@ -0,0 +1,288 @@
+/**
+ * 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.configuration.spring;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import org.apache.cxf.common.injection.NoJSR250Annotations;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.configuration.Configurable;
+import org.apache.cxf.configuration.Configurer;
+import org.apache.cxf.extension.BusExtension;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.AbstractBeanFactory;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.wiring.BeanConfigurerSupport;
+import org.springframework.beans.factory.wiring.BeanWiringInfo;
+import org.springframework.beans.factory.wiring.BeanWiringInfoResolver;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ConfigurableApplicationContext;
+
+@NoJSR250Annotations
+public class ConfigurerImpl extends BeanConfigurerSupport
+ implements Configurer, ApplicationContextAware, BusExtension {
+
+ private static final Logger LOG = LogUtils.getL7dLogger(ConfigurerImpl.class);
+
+ private Set<ApplicationContext> appContexts;
+ private final Map<String, List<MatcherHolder>> wildCardBeanDefinitions = new TreeMap<>();
+ private BeanFactory beanFactory;
+
+ static class MatcherHolder implements Comparable<MatcherHolder> {
+ Matcher matcher;
+ String wildCardId;
+ MatcherHolder(String orig, Matcher matcher) {
+ wildCardId = orig;
+ this.matcher = matcher;
+ }
+
+ @Override
+ public int compareTo(MatcherHolder mh) {
+ int literalCharsLen1 = this.wildCardId.replace("*", "").length();
+ int literalCharsLen2 = mh.wildCardId.replace("*", "").length();
+ // The expression with more literal characters should end up on the top of the list
+ return Integer.compare(literalCharsLen1, literalCharsLen2) * -1;
+ }
+ }
+
+ public ConfigurerImpl() {
+ // complete
+ }
+
+ public ConfigurerImpl(ApplicationContext ac) {
+ setApplicationContext(ac);
+ }
+
+ @Override
+ public void setBeanFactory(BeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ super.setBeanFactory(beanFactory);
+ }
+
+ private void initWildcardDefinitionMap() {
+ if (null != appContexts) {
+ for (ApplicationContext appContext : appContexts) {
+ for (String n : appContext.getBeanDefinitionNames()) {
+ if (isWildcardBeanName(n)) {
+ AutowireCapableBeanFactory bf = appContext.getAutowireCapableBeanFactory();
+ BeanDefinitionRegistry bdr = (BeanDefinitionRegistry) bf;
+ BeanDefinition bd = bdr.getBeanDefinition(n);
+ String className = bd.getBeanClassName();
+ if (null != className) {
+ final String name = n.charAt(0) != '*' ? n
+ : "." + n.replaceAll("\\.", "\\."); //old wildcard
+ try {
+ Matcher matcher = Pattern.compile(name).matcher("");
+ List<MatcherHolder> m = wildCardBeanDefinitions.get(className);
+ if (m == null) {
+ m = new ArrayList<>();
+ wildCardBeanDefinitions.put(className, m);
+ }
+ MatcherHolder holder = new MatcherHolder(n, matcher);
+ m.add(holder);
+ } catch (PatternSyntaxException npe) {
+ //not a valid patter, we'll ignore
+ }
+ } else {
+ LogUtils.log(LOG, Level.WARNING, "WILDCARD_BEAN_ID_WITH_NO_CLASS_MSG", n);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public void configureBean(Object beanInstance) {
+ configureBean(null, beanInstance, true);
+ }
+
+ public void configureBean(String bn, Object beanInstance) {
+ configureBean(bn, beanInstance, true);
+ }
+ public synchronized void configureBean(String bn, Object beanInstance, boolean checkWildcards) {
+
+ if (null == appContexts) {
+ return;
+ }
+
+ if (null == bn) {
+ bn = getBeanName(beanInstance);
+ if (null == bn) {
+ return;
+ }
+ }
+
+ if (checkWildcards) {
+ configureWithWildCard(bn, beanInstance);
+ }
+
+ final String beanName = bn;
+ setBeanWiringInfoResolver(new BeanWiringInfoResolver() {
+ public BeanWiringInfo resolveWiringInfo(Object instance) {
+ if (!beanName.isEmpty()) {
+ return new BeanWiringInfo(beanName);
+ }
+ return null;
+ }
+ });
+
+ for (ApplicationContext appContext : appContexts) {
+ if (appContext.containsBean(bn)) {
+ this.setBeanFactory(appContext.getAutowireCapableBeanFactory());
+ }
+ }
+
+ try {
+ //this will prevent a call into the AbstractBeanFactory.markBeanAsCreated(...)
+ //which saves ALL the names into a HashSet. For URL based configuration,
+ //this can leak memory
+ if (beanFactory instanceof AbstractBeanFactory) {
+ ((AbstractBeanFactory)beanFactory).getMergedBeanDefinition(bn);
+ }
+ super.configureBean(beanInstance);
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Successfully performed injection.");
+ }
+ } catch (NoSuchBeanDefinitionException ex) {
+ // users often wonder why the settings in their configuration files seem
+ // to have no effect - the most common cause is that they have been using
+ // incorrect bean ids
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.log(Level.FINE, "NO_MATCHING_BEAN_MSG", beanName);
+ }
+ }
+ }
+
+ private void configureWithWildCard(String bn, Object beanInstance) {
+ if (!wildCardBeanDefinitions.isEmpty()) {
+ Class<?> clazz = beanInstance.getClass();
+ while (!Object.class.equals(clazz)) {
+ String className = clazz.getName();
+ List<MatcherHolder> matchers = wildCardBeanDefinitions.get(className);
+ if (matchers != null) {
+ for (MatcherHolder m : matchers) {
+ synchronized (m.matcher) {
+ m.matcher.reset(bn);
+ if (m.matcher.matches()) {
+ configureBean(m.wildCardId, beanInstance, false);
+ return;
+ }
+ }
+ }
+ }
+ clazz = clazz.getSuperclass();
+ }
+ }
+ }
+
+ private boolean isWildcardBeanName(String bn) {
+ return bn.indexOf('*') != -1 || bn.indexOf('?') != -1
+ || (bn.indexOf('(') != -1 && bn.indexOf(')') != -1);
+ }
+
+ protected String getBeanName(Object beanInstance) {
+ if (beanInstance instanceof Configurable) {
+ return ((Configurable)beanInstance).getBeanName();
+ }
+ String beanName = null;
+ Method m = null;
+ try {
+ m = beanInstance.getClass().getDeclaredMethod("getBeanName", (Class[])null);
+ } catch (NoSuchMethodException ex) {
+ try {
+ m = beanInstance.getClass().getMethod("getBeanName", (Class[])null);
+ } catch (NoSuchMethodException e) {
+ //ignore
+ }
+ }
+ if (m != null) {
+ try {
+ beanName = (String)(m.invoke(beanInstance));
+ } catch (Exception ex) {
+ LogUtils.log(LOG, Level.WARNING, "ERROR_DETERMINING_BEAN_NAME_EXC", ex);
+ }
+ }
+
+ if (null == beanName) {
+ LogUtils.log(LOG, Level.FINE, "COULD_NOT_DETERMINE_BEAN_NAME_MSG",
+ beanInstance.getClass().getName());
+ }
+
+ return beanName;
+ }
+
+ public final void setApplicationContext(ApplicationContext ac) {
+ appContexts = new CopyOnWriteArraySet<>();
+ addApplicationContext(ac);
+ this.beanFactory = ac.getAutowireCapableBeanFactory();
+ super.setBeanFactory(this.beanFactory);
+ }
+
+ public final void addApplicationContext(ApplicationContext ac) {
+ if (!appContexts.contains(ac)) {
+ appContexts.add(ac);
+ List<ApplicationContext> inactiveApplicationContexts = new ArrayList<>();
+ Iterator<ApplicationContext> it = appContexts.iterator();
+ while (it.hasNext()) {
+ ApplicationContext c = it.next();
+ if (c instanceof ConfigurableApplicationContext
+ && !((ConfigurableApplicationContext)c).isActive()) {
+ inactiveApplicationContexts.add(c);
+ }
+ }
+ // Remove the inactive application context here can avoid the UnsupportedOperationException
+ for (ApplicationContext context : inactiveApplicationContexts) {
+ appContexts.remove(context);
+ }
+ initWildcardDefinitionMap();
+ }
+ }
+
+ @Override
+ public void destroy() {
+ super.destroy();
+ appContexts.clear();
+ }
+
+ public Class<?> getRegistrationType() {
+ return Configurer.class;
+ }
+
+ protected Set<ApplicationContext> getAppContexts() {
+ return appContexts;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/databinding/source/SourceDataBinding.java b/transform/src/patch/java/org/apache/cxf/databinding/source/SourceDataBinding.java
new file mode 100644
index 0000000..5321779
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/databinding/source/SourceDataBinding.java
@@ -0,0 +1,104 @@
+/**
+ * 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.databinding.source;
+
+
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.w3c.dom.Node;
+
+import org.apache.cxf.common.xmlschema.SchemaCollection;
+import org.apache.cxf.databinding.DataReader;
+import org.apache.cxf.databinding.DataWriter;
+import org.apache.cxf.service.Service;
+import org.apache.cxf.service.ServiceModelVisitor;
+import org.apache.cxf.service.model.MessagePartInfo;
+import org.apache.cxf.service.model.ServiceInfo;
+import org.apache.ws.commons.schema.constants.Constants;
+
+/**
+ * A simple databinding implementation which reads and writes Source objects.
+ */
+public class SourceDataBinding extends org.apache.cxf.databinding.AbstractDataBinding {
+
+ public static final String PREFERRED_FORMAT = "source-preferred-format";
+
+ final Class<?> preferred;
+
+ public SourceDataBinding() {
+ super();
+ preferred = null;
+ }
+ public SourceDataBinding(Class<?> pref) {
+ super();
+ preferred = pref;
+ }
+
+ public void initialize(Service service) {
+ for (ServiceInfo serviceInfo : service.getServiceInfos()) {
+ SchemaCollection schemaCollection = serviceInfo.getXmlSchemaCollection();
+ if (schemaCollection.getXmlSchemas().length > 1) {
+ // Schemas are already populated.
+ continue;
+ }
+ new ServiceModelVisitor(serviceInfo) {
+ @Override
+ public void begin(MessagePartInfo part) {
+ if (part.getTypeQName() != null || part.getElementQName() != null) {
+ return;
+ }
+ part.setTypeQName(Constants.XSD_ANYTYPE);
+ }
+ } .walk();
+ }
+ }
+
+
+ @SuppressWarnings("unchecked")
+ public <T> DataReader<T> createReader(Class<T> cls) {
+ if (cls == XMLStreamReader.class) {
+ return (DataReader<T>) new XMLStreamDataReader(preferred);
+ } else if (cls == Node.class) {
+ return (DataReader<T>) new NodeDataReader();
+ } else {
+ throw new UnsupportedOperationException("The type " + cls.getName() + " is not supported.");
+ }
+ }
+
+ public Class<?>[] getSupportedReaderFormats() {
+ return new Class<?>[] {XMLStreamReader.class, Node.class};
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> DataWriter<T> createWriter(Class<T> cls) {
+ if (cls == XMLStreamWriter.class) {
+ return (DataWriter<T>) new XMLStreamDataWriter();
+ } else if (cls == Node.class) {
+ return (DataWriter<T>) new NodeDataWriter();
+ }
+ throw new UnsupportedOperationException("The type " + cls.getName() + " is not supported.");
+ }
+
+ public Class<?>[] getSupportedWriterFormats() {
+ return new Class<?>[] {XMLStreamWriter.class, Node.class};
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/databinding/stax/StaxDataBinding.java b/transform/src/patch/java/org/apache/cxf/databinding/stax/StaxDataBinding.java
new file mode 100644
index 0000000..3cb836b
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/databinding/stax/StaxDataBinding.java
@@ -0,0 +1,187 @@
+/**
+ * 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.databinding.stax;
+
+import java.util.Collection;
+import java.util.logging.Logger;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.validation.Schema;
+
+import org.w3c.dom.Node;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.xmlschema.SchemaCollection;
+import org.apache.cxf.databinding.AbstractInterceptorProvidingDataBinding;
+import org.apache.cxf.databinding.DataReader;
+import org.apache.cxf.databinding.DataWriter;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.interceptor.StaxInEndingInterceptor;
+import org.apache.cxf.message.Attachment;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.AbstractPhaseInterceptor;
+import org.apache.cxf.phase.Phase;
+import org.apache.cxf.service.Service;
+import org.apache.cxf.service.ServiceModelVisitor;
+import org.apache.cxf.service.model.MessagePartInfo;
+import org.apache.cxf.service.model.ServiceInfo;
+import org.apache.cxf.staxutils.StaxUtils;
+import org.apache.ws.commons.schema.constants.Constants;
+
+/**
+ * A simple databinding implementation which reads and writes Source objects.
+ * This will not work with the standard databinding interceptors.
+ */
+public class StaxDataBinding extends AbstractInterceptorProvidingDataBinding {
+
+ private XMLStreamDataReader xsrReader;
+ private XMLStreamDataWriter xswWriter;
+
+ public StaxDataBinding() {
+ super();
+ this.xsrReader = new XMLStreamDataReader();
+ this.xswWriter = new XMLStreamDataWriter();
+ inInterceptors.add(new StaxInEndingInterceptor(Phase.POST_INVOKE));
+ inFaultInterceptors.add(new StaxInEndingInterceptor(Phase.POST_INVOKE));
+
+ inInterceptors.add(RemoveStaxInEndingInterceptor.INSTANCE);
+ inFaultInterceptors.add(RemoveStaxInEndingInterceptor.INSTANCE);
+ }
+
+ static class RemoveStaxInEndingInterceptor extends AbstractPhaseInterceptor<Message> {
+ static final RemoveStaxInEndingInterceptor INSTANCE = new RemoveStaxInEndingInterceptor();
+
+ RemoveStaxInEndingInterceptor() {
+ super(Phase.PRE_INVOKE);
+ addBefore(StaxInEndingInterceptor.class.getName());
+ }
+
+ public void handleMessage(Message message) {
+ message.getInterceptorChain().remove(StaxInEndingInterceptor.INSTANCE);
+ }
+ }
+
+
+ public void initialize(Service service) {
+ for (ServiceInfo serviceInfo : service.getServiceInfos()) {
+ SchemaCollection schemaCollection = serviceInfo.getXmlSchemaCollection();
+ if (schemaCollection.getXmlSchemas().length > 1) {
+ // Schemas are already populated.
+ continue;
+ }
+ new ServiceModelVisitor(serviceInfo) {
+
+ @Override
+ public void begin(MessagePartInfo part) {
+ if (part.getTypeQName() != null || part.getElementQName() != null) {
+ return;
+ }
+ part.setTypeQName(Constants.XSD_ANYTYPE);
+ }
+ } .walk();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> DataReader<T> createReader(Class<T> cls) {
+ if (cls == XMLStreamReader.class) {
+ return (DataReader<T>) xsrReader;
+ }
+ throw new UnsupportedOperationException("The type " + cls.getName() + " is not supported.");
+ }
+
+ public Class<?>[] getSupportedReaderFormats() {
+ return new Class<?>[] {XMLStreamReader.class};
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> DataWriter<T> createWriter(Class<T> cls) {
+ if (cls == XMLStreamWriter.class) {
+ return (DataWriter<T>) xswWriter;
+ }
+ throw new UnsupportedOperationException("The type " + cls.getName() + " is not supported.");
+ }
+
+ public Class<?>[] getSupportedWriterFormats() {
+ return new Class<?>[] {XMLStreamWriter.class, Node.class};
+ }
+
+ public static class XMLStreamDataReader implements DataReader<XMLStreamReader> {
+
+ public Object read(MessagePartInfo part, XMLStreamReader input) {
+ return read(null, input, part.getTypeClass());
+ }
+
+ public Object read(QName name, XMLStreamReader input, Class<?> type) {
+ return input;
+ }
+
+ public Object read(XMLStreamReader reader) {
+ return reader;
+ }
+
+ public void setSchema(Schema s) {
+ }
+
+ public void setAttachments(Collection<Attachment> attachments) {
+ }
+
+ public void setProperty(String prop, Object value) {
+ }
+ }
+
+ public static class XMLStreamDataWriter implements DataWriter<XMLStreamWriter> {
+ private static final Logger LOG = LogUtils.getL7dLogger(XMLStreamDataWriter.class);
+
+ public void write(Object obj, MessagePartInfo part, XMLStreamWriter output) {
+ write(obj, output);
+ }
+
+ public void write(Object obj, XMLStreamWriter writer) {
+ try {
+ if (obj instanceof XMLStreamReader) {
+ XMLStreamReader xmlStreamReader = (XMLStreamReader) obj;
+ StaxUtils.copy(xmlStreamReader, writer);
+ xmlStreamReader.close();
+ } else if (obj instanceof XMLStreamWriterCallback) {
+ ((XMLStreamWriterCallback) obj).write(writer);
+ } else {
+ throw new UnsupportedOperationException("Data types of "
+ + obj.getClass() + " are not supported.");
+ }
+ } catch (XMLStreamException e) {
+ throw new Fault("COULD_NOT_READ_XML_STREAM", LOG, e);
+ }
+ }
+
+ public void setSchema(Schema s) {
+ }
+
+ public void setAttachments(Collection<Attachment> attachments) {
+ }
+
+ public void setProperty(String key, Object value) {
+ }
+
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/endpoint/AbstractConduitSelector.java b/transform/src/patch/java/org/apache/cxf/endpoint/AbstractConduitSelector.java
new file mode 100644
index 0000000..250e523
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/endpoint/AbstractConduitSelector.java
@@ -0,0 +1,308 @@
+/**
+ * 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.endpoint;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.logging.Logger;
+
+import org.apache.cxf.BusException;
+import org.apache.cxf.common.util.PropertyUtils;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageUtils;
+import org.apache.cxf.service.model.EndpointInfo;
+import org.apache.cxf.transport.Conduit;
+import org.apache.cxf.transport.ConduitInitiator;
+import org.apache.cxf.transport.ConduitInitiatorManager;
+import org.apache.cxf.transport.MessageObserver;
+import org.apache.cxf.ws.addressing.AttributedURIType;
+import org.apache.cxf.ws.addressing.EndpointReferenceType;
+
+
+/**
+ * Abstract base class holding logic common to any ConduitSelector
+ * that retrieves a Conduit from the ConduitInitiator.
+ */
+public abstract class AbstractConduitSelector implements ConduitSelector, Closeable {
+ public static final String CONDUIT_COMPARE_FULL_URL
+ = "org.apache.cxf.ConduitSelector.compareFullUrl";
+ protected static final String KEEP_CONDUIT_ALIVE = "KeepConduitAlive";
+
+
+ //collection of conduits that were created so we can close them all at the end
+ protected List<Conduit> conduits = new CopyOnWriteArrayList<>();
+
+
+ protected Endpoint endpoint;
+
+
+ public AbstractConduitSelector() {
+ }
+
+ /**
+ * Constructor, allowing a specific conduit to override normal selection.
+ *
+ * @param c specific conduit
+ */
+ public AbstractConduitSelector(Conduit c) {
+ if (c != null) {
+ conduits.add(c);
+ }
+ }
+
+ public void close() {
+ for (Conduit c : conduits) {
+ c.close();
+ }
+ conduits.clear();
+ }
+
+ protected void removeConduit(Conduit conduit) {
+ if (conduit != null) {
+ conduit.close();
+ conduits.remove(conduit);
+ }
+ }
+
+ /**
+ * Mechanics to actually get the Conduit from the ConduitInitiator
+ * if necessary.
+ *
+ * @param message the current Message
+ */
+ protected Conduit getSelectedConduit(Message message) {
+ Conduit c = findCompatibleConduit(message);
+ if (c == null) {
+ Exchange exchange = message.getExchange();
+ EndpointInfo ei = endpoint.getEndpointInfo();
+ String transportID = ei.getTransportId();
+ try {
+ ConduitInitiatorManager conduitInitiatorMgr = exchange.getBus()
+ .getExtension(ConduitInitiatorManager.class);
+ if (conduitInitiatorMgr != null) {
+ ConduitInitiator conduitInitiator =
+ conduitInitiatorMgr.getConduitInitiator(transportID);
+ if (conduitInitiator != null) {
+ c = createConduit(message, exchange, conduitInitiator);
+ } else {
+ getLogger().warning("ConduitInitiator not found: "
+ + ei.getAddress());
+ }
+ } else {
+ getLogger().warning("ConduitInitiatorManager not found");
+ }
+ } catch (BusException | IOException ex) {
+ throw new Fault(ex);
+ }
+ }
+ if (c != null && c.getTarget() != null && c.getTarget().getAddress() != null) {
+ replaceEndpointAddressPropertyIfNeeded(message, c.getTarget().getAddress().getValue(), c);
+ }
+ //the search for the conduit could cause extra properties to be reset/loaded.
+ message.resetContextCache();
+ message.put(Conduit.class, c);
+ return c;
+ }
+
+ protected Conduit createConduit(Message message, Exchange exchange, ConduitInitiator conduitInitiator)
+ throws IOException {
+ Conduit c;
+ synchronized (endpoint) {
+ if (!conduits.isEmpty()) {
+ c = findCompatibleConduit(message);
+ if (c != null) {
+ return c;
+ }
+ }
+ EndpointInfo ei = endpoint.getEndpointInfo();
+ String add = (String)message.get(Message.ENDPOINT_ADDRESS);
+ String basePath = (String)message.get(Message.BASE_PATH);
+ if (StringUtils.isEmpty(add)
+ || add.equals(ei.getAddress())) {
+ c = conduitInitiator.getConduit(ei, exchange.getBus());
+ replaceEndpointAddressPropertyIfNeeded(message, add, c);
+ } else {
+ EndpointReferenceType epr = new EndpointReferenceType();
+ AttributedURIType ad = new AttributedURIType();
+ ad.setValue(StringUtils.isEmpty(basePath) ? add : basePath);
+ epr.setAddress(ad);
+ c = conduitInitiator.getConduit(ei, epr, exchange.getBus());
+ }
+ MessageObserver observer =
+ exchange.get(MessageObserver.class);
+ if (observer != null) {
+ c.setMessageObserver(observer);
+ } else {
+ getLogger().warning("MessageObserver not found");
+ }
+ conduits.add(c);
+ }
+ return c;
+ }
+
+ // Some conduits may replace the endpoint address after it has already been prepared
+ // but before the invocation has been done (ex, org.apache.cxf.clustering.LoadDistributorTargetSelector)
+ // which may affect JAX-RS clients where actual endpoint address property may include additional path
+ // segments.
+ protected boolean replaceEndpointAddressPropertyIfNeeded(Message message,
+ String endpointAddress,
+ Conduit cond) {
+ return false;
+ }
+
+ /**
+ * @return the encapsulated Endpoint
+ */
+ public Endpoint getEndpoint() {
+ return endpoint;
+ }
+
+ /**
+ * @param ep the endpoint to encapsulate
+ */
+ public void setEndpoint(Endpoint ep) {
+ endpoint = ep;
+ }
+
+ /**
+ * Called on completion of the MEP for which the Conduit was required.
+ *
+ * @param exchange represents the completed MEP
+ */
+ public void complete(Exchange exchange) {
+ // Clients expecting explicit InputStream responses
+ // will need to keep low level conduits operating on InputStreams open
+ // and will be responsible for closing the streams
+
+ if (PropertyUtils.isTrue(exchange.get(KEEP_CONDUIT_ALIVE))) {
+ return;
+ }
+ try {
+ if (exchange.getInMessage() != null) {
+ Conduit c = exchange.getOutMessage().get(Conduit.class);
+ if (c == null) {
+ getSelectedConduit(exchange.getInMessage()).close(exchange.getInMessage());
+ } else {
+ c.close(exchange.getInMessage());
+ }
+ }
+ } catch (IOException e) {
+ //IGNORE
+ }
+ }
+ /**
+ * @return the logger to use
+ */
+ protected abstract Logger getLogger();
+
+ /**
+ * If address protocol was changed, conduit should be re-initialised
+ *
+ * @param message the current Message
+ */
+ protected Conduit findCompatibleConduit(Message message) {
+ Conduit c = message.get(Conduit.class);
+ if (c == null
+ && message.getExchange() != null
+ && message.getExchange().getOutMessage() != null
+ && message.getExchange().getOutMessage() != message) {
+ c = message.getExchange().getOutMessage().get(Conduit.class);
+ }
+ if (c != null) {
+ return c;
+ }
+ ContextualBooleanGetter cbg = new ContextualBooleanGetter(message);
+ for (Conduit c2 : conduits) {
+ if (c2.getTarget() == null
+ || c2.getTarget().getAddress() == null
+ || c2.getTarget().getAddress().getValue() == null) {
+ continue;
+ }
+ String conduitAddress = c2.getTarget().getAddress().getValue();
+
+ EndpointInfo ei = endpoint.getEndpointInfo();
+ String actualAddress = ei.getAddress();
+
+ String messageAddress = (String)message.get(Message.ENDPOINT_ADDRESS);
+ if (messageAddress != null) {
+ actualAddress = messageAddress;
+ }
+
+ if (matchAddresses(conduitAddress, actualAddress, cbg)) {
+ return c2;
+ }
+ }
+ for (Conduit c2 : conduits) {
+ if (c2.getTarget() == null
+ || c2.getTarget().getAddress() == null
+ || c2.getTarget().getAddress().getValue() == null) {
+ return c2;
+ }
+ }
+ return null;
+ }
+
+ private boolean matchAddresses(String conduitAddress, String actualAddress, ContextualBooleanGetter cbg) {
+ if (conduitAddress.length() == actualAddress.length()) {
+ //let's be optimistic and try full comparison first, regardless of CONDUIT_COMPARE_FULL_URL value,
+ //which can be expensive to fetch; as a matter of fact, anyway, if the addresses fully match,
+ //their hosts also match
+ if (conduitAddress.equalsIgnoreCase(actualAddress)) {
+ return true;
+ }
+ return !cbg.isFullComparison() && matchAddressSubstrings(conduitAddress, actualAddress);
+ }
+ return !cbg.isFullComparison() && matchAddressSubstrings(conduitAddress, actualAddress);
+ }
+
+ //smart address substring comparison that tries to avoid building and comparing substrings unless strictly required
+ private boolean matchAddressSubstrings(String conduitAddress, String actualAddress) {
+ int idx = conduitAddress.indexOf(':');
+ if (idx == actualAddress.indexOf(':')) {
+ if (idx <= 0) {
+ return true;
+ }
+ return conduitAddress.substring(0, idx).equalsIgnoreCase(actualAddress.substring(0, idx));
+ }
+ //no possible match as for sure the substrings before idx will be different
+ return false;
+ }
+
+ private static final class ContextualBooleanGetter {
+ private Boolean value;
+ private final Message message;
+
+ ContextualBooleanGetter(Message message) {
+ this.message = message;
+ }
+
+ public boolean isFullComparison() {
+ if (value == null) {
+ value = MessageUtils.getContextualBoolean(message, CONDUIT_COMPARE_FULL_URL, false);
+ }
+ return value;
+ }
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/endpoint/ClientCallback.java b/transform/src/patch/java/org/apache/cxf/endpoint/ClientCallback.java
new file mode 100644
index 0000000..467b5a8
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/endpoint/ClientCallback.java
@@ -0,0 +1,166 @@
+/**
+ * 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.endpoint;
+
+import java.util.Map;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.cxf.message.Message;
+
+/**
+ * Asynchronous callback object for calls to {@link Client#invoke(ClientCallback, String, Object...)}
+ * and related functions.
+ *
+ * The default behavior of this expects the following pattern:
+ * <ol>
+ * <li>ClientCallback cb = new ClientCallback();<>
+ * <li>client.invoke(cb, "someMethod", ....);</li>
+ * <li>cb.wait();</li>
+ * <li>// CXF calls notify on the callback object when the operation is complete.</li>
+ * </ol>
+ */
+public class ClientCallback implements Future<Object[]> {
+ protected final CompletableFuture<Object[]> delegate = new CompletableFuture<>();
+ protected Map<String, Object> context;
+ protected boolean started;
+
+ public ClientCallback() {
+ }
+
+ /**
+ * Called when a message is first received prior to any actions
+ * being applied to the message. The InterceptorChain is setup so
+ * modifications to that can be done.
+ */
+ public void start(Message msg) {
+ started = true;
+ }
+
+ /**
+ * If the processing of the incoming message proceeds normally, this
+ * method is called with the response context values and the resulting objects.
+ *
+ * The default behavior just stores the objects and calls notifyAll to wake
+ * up threads waiting for the response.
+ *
+ * @param ctx
+ * @param res
+ */
+ public void handleResponse(Map<String, Object> ctx, Object[] res) {
+ context = ctx;
+ delegate.complete(res);
+
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+
+ /**
+ * If processing of the incoming message results in an exception, this
+ * method is called with the resulting exception.
+ *
+ * The default behavior just stores the objects and calls notifyAll to wake
+ * up threads waiting for the response.
+ *
+ * @param ctx
+ * @param ex
+ */
+ public void handleException(Map<String, Object> ctx, Throwable ex) {
+ context = ctx;
+ delegate.completeExceptionally(ex);
+
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+
+
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ if (!started) {
+ delegate.cancel(mayInterruptIfRunning);
+
+ synchronized (this) {
+ notifyAll();
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * return the map of items returned from an operation.
+ * @return the response context
+ * @throws InterruptedException if the operation was cancelled.
+ * @throws ExecutionException if the operation resulted in a fault.
+ */
+ public Map<String, Object> getResponseContext() throws InterruptedException, ExecutionException {
+ synchronized (this) {
+ if (!delegate.isDone()) {
+ wait();
+ }
+ }
+ if (delegate.isCancelled()) {
+ throw new InterruptedException("Operation Cancelled");
+ }
+ if (delegate.isCompletedExceptionally()) {
+ delegate.get();
+ }
+ return context;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object[] get() throws InterruptedException, ExecutionException {
+ try {
+ return delegate.get();
+ } catch (final CancellationException ex) {
+ // Preserving the exception raised by former implementation
+ throw new InterruptedException("Operation has been cancelled");
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object[] get(long timeout, TimeUnit unit) throws InterruptedException,
+ ExecutionException, TimeoutException {
+ try {
+ return delegate.get(timeout, unit);
+ } catch (final CancellationException ex) {
+ // Preserving the exception raised by former implementation
+ throw new InterruptedException("Operation has been cancelled");
+ }
+ }
+
+ public boolean isCancelled() {
+ return delegate.isCancelled();
+ }
+
+ public boolean isDone() {
+ return delegate.isDone();
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/endpoint/ClientImpl.java b/transform/src/patch/java/org/apache/cxf/endpoint/ClientImpl.java
new file mode 100644
index 0000000..53d3a43
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/endpoint/ClientImpl.java
@@ -0,0 +1,1192 @@
+/**
+ * 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.endpoint;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.xml.namespace.QName;
+import javax.xml.ws.handler.MessageContext;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.binding.Binding;
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.classloader.ClassLoaderUtils.ClassLoaderHolder;
+import org.apache.cxf.common.i18n.UncheckedException;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.PropertyUtils;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.interceptor.AbstractBasicInterceptorProvider;
+import org.apache.cxf.interceptor.ClientOutFaultObserver;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.interceptor.Interceptor;
+import org.apache.cxf.interceptor.InterceptorChain;
+import org.apache.cxf.interceptor.InterceptorProvider;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.ExchangeImpl;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageContentsList;
+import org.apache.cxf.message.MessageImpl;
+import org.apache.cxf.phase.PhaseChainCache;
+import org.apache.cxf.phase.PhaseInterceptorChain;
+import org.apache.cxf.phase.PhaseManager;
+import org.apache.cxf.service.Service;
+import org.apache.cxf.service.model.BindingInfo;
+import org.apache.cxf.service.model.BindingMessageInfo;
+import org.apache.cxf.service.model.BindingOperationInfo;
+import org.apache.cxf.service.model.EndpointInfo;
+import org.apache.cxf.service.model.MessageInfo;
+import org.apache.cxf.service.model.ServiceInfo;
+import org.apache.cxf.transport.Conduit;
+import org.apache.cxf.transport.MessageObserver;
+import org.apache.cxf.workqueue.SynchronousExecutor;
+
+public class ClientImpl
+ extends AbstractBasicInterceptorProvider
+ implements Client, Retryable, MessageObserver {
+
+ public static final String THREAD_LOCAL_REQUEST_CONTEXT = "thread.local.request.context";
+ /**
+ * When a synchronous request/response invoke is done using an asynchronous transport mechanism,
+ * this is the timeout used for waiting for the response. Default is 60 seconds.
+ */
+ public static final String SYNC_TIMEOUT = "cxf.synchronous.timeout";
+
+ public static final String FINISHED = "exchange.finished";
+
+ private static final Logger LOG = LogUtils.getL7dLogger(ClientImpl.class);
+
+ protected Bus bus;
+ protected ConduitSelector conduitSelector;
+ protected ClientOutFaultObserver outFaultObserver;
+ protected int synchronousTimeout = 60000; // default 60 second timeout
+
+ protected PhaseChainCache outboundChainCache = new PhaseChainCache();
+ protected PhaseChainCache inboundChainCache = new PhaseChainCache();
+
+ protected Map<String, Object> currentRequestContext = new ConcurrentHashMap<>(8, 0.75f, 4);
+ protected Thread latestContextThread;
+ protected Map<Thread, EchoContext> requestContext
+ = Collections.synchronizedMap(new WeakHashMap<Thread, EchoContext>());
+
+ protected Map<Thread, ResponseContext> responseContext
+ = Collections.synchronizedMap(new WeakHashMap<Thread, ResponseContext>());
+
+ protected Executor executor;
+
+ public ClientImpl(Bus b, Endpoint e) {
+ this(b, e, (ConduitSelector)null);
+ }
+
+ public ClientImpl(Bus b, Endpoint e, Conduit c) {
+ this(b, e, new PreexistingConduitSelector(c));
+ }
+
+ public ClientImpl(Bus b, Endpoint e, ConduitSelector sc) {
+ bus = b;
+ outFaultObserver = new ClientOutFaultObserver(bus);
+ getConduitSelector(sc).setEndpoint(e);
+ notifyLifecycleManager();
+ }
+
+ /**
+ * Create a Client that uses a specific EndpointImpl.
+ * @param bus
+ * @param svc
+ * @param port
+ * @param endpointImplFactory
+ */
+ public ClientImpl(Bus bus, Service svc, QName port,
+ EndpointImplFactory endpointImplFactory) {
+ this.bus = bus;
+ outFaultObserver = new ClientOutFaultObserver(bus);
+ EndpointInfo epfo = findEndpoint(svc, port);
+
+ try {
+ if (endpointImplFactory != null) {
+ getConduitSelector().setEndpoint(endpointImplFactory.newEndpointImpl(bus, svc, epfo));
+ } else {
+ getConduitSelector().setEndpoint(new EndpointImpl(bus, svc, epfo));
+ }
+ } catch (EndpointException epex) {
+ throw new IllegalStateException("Unable to create endpoint: " + epex.getMessage(), epex);
+ }
+ notifyLifecycleManager();
+ }
+
+ public Bus getBus() {
+ return bus;
+ }
+
+ public void destroy() {
+ if (bus == null) {
+ return;
+ }
+ if (getEndpoint() != null) {
+ for (Closeable c : getEndpoint().getCleanupHooks()) {
+ try {
+ c.close();
+ } catch (IOException e) {
+ //ignore
+ }
+ }
+ }
+ ClientLifeCycleManager mgr = bus.getExtension(ClientLifeCycleManager.class);
+ if (null != mgr) {
+ mgr.clientDestroyed(this);
+ }
+
+ if (conduitSelector != null) {
+ if (conduitSelector instanceof Closeable) {
+ try {
+ ((Closeable)conduitSelector).close();
+ } catch (IOException e) {
+ //ignore, we're destroying anyway
+ }
+ } else {
+ getConduit().close();
+ }
+ }
+
+ bus = null;
+ conduitSelector = null;
+ outFaultObserver = null;
+ outboundChainCache = null;
+ inboundChainCache = null;
+
+ currentRequestContext = null;
+ requestContext.clear();
+ requestContext = null;
+ responseContext.clear();
+ responseContext = null;
+ executor = null;
+ }
+
+ private void notifyLifecycleManager() {
+ ClientLifeCycleManager mgr = bus.getExtension(ClientLifeCycleManager.class);
+ if (null != mgr) {
+ mgr.clientCreated(this);
+ }
+ }
+
+ private EndpointInfo findEndpoint(Service svc, QName port) {
+ if (port != null) {
+ EndpointInfo epfo = svc.getEndpointInfo(port);
+ if (epfo == null) {
+ throw new IllegalArgumentException("The service " + svc.getName()
+ + " does not have an endpoint " + port + ".");
+ }
+ return epfo;
+ }
+
+ for (ServiceInfo svcfo : svc.getServiceInfos()) {
+ for (EndpointInfo e : svcfo.getEndpoints()) {
+ BindingInfo bfo = e.getBinding();
+ String bid = bfo.getBindingId();
+ if ("http://schemas.xmlsoap.org/wsdl/soap/".equals(bid)
+ || "http://schemas.xmlsoap.org/wsdl/soap12/".equals(bid)) {
+ for (Object o : bfo.getExtensors().get()) {
+ try {
+ String s = (String)o.getClass().getMethod("getTransportURI").invoke(o);
+ if (s != null && s.endsWith("http")) {
+ return e;
+ }
+ } catch (Throwable t) {
+ //ignore
+ }
+ }
+ }
+ }
+ }
+ throw new UnsupportedOperationException(
+ "Only document-style SOAP 1.1 and 1.2 http are supported "
+ + "for auto-selection of endpoint; none were found.");
+ }
+
+ public Endpoint getEndpoint() {
+ return getConduitSelector().getEndpoint();
+ }
+
+ public void releaseThreadContexts() {
+ final Thread t = Thread.currentThread();
+ requestContext.remove(t);
+ responseContext.remove(t);
+ }
+
+ @Override
+ public Contexts getContexts() {
+ return new Contexts() {
+ @Override
+ public void close() throws Exception {
+ releaseThreadContexts();
+ }
+ @Override
+ public Map<String, Object> getRequestContext() {
+ return ClientImpl.this.getRequestContext();
+ }
+ @Override
+ public Map<String, Object> getResponseContext() {
+ return ClientImpl.this.getResponseContext();
+ }
+ };
+ }
+
+ public Map<String, Object> getRequestContext() {
+ if (isThreadLocalRequestContext()) {
+ final Thread t = Thread.currentThread();
+ requestContext.computeIfAbsent(t, k -> new EchoContext(currentRequestContext));
+ latestContextThread = t;
+ return requestContext.get(t);
+ }
+ return currentRequestContext;
+ }
+ public Map<String, Object> getResponseContext() {
+ if (!responseContext.containsKey(Thread.currentThread())) {
+ final Thread t = Thread.currentThread();
+ responseContext.put(t, new ResponseContext(responseContext));
+ }
+ return responseContext.get(Thread.currentThread());
+ }
+ protected Map<String, Object> setResponseContext(Map<String, Object> ctx) {
+ if (ctx instanceof ResponseContext) {
+ ResponseContext c = (ResponseContext)ctx;
+ responseContext.put(Thread.currentThread(), c);
+ return c;
+ }
+ ResponseContext c = new ResponseContext(ctx, responseContext);
+ responseContext.put(Thread.currentThread(), c);
+ return c;
+ }
+ public boolean isThreadLocalRequestContext() {
+ Object o = currentRequestContext.get(THREAD_LOCAL_REQUEST_CONTEXT);
+ if (o != null) {
+ final boolean local;
+ if (o instanceof Boolean) {
+ local = ((Boolean)o).booleanValue();
+ } else {
+ local = Boolean.parseBoolean(o.toString());
+ }
+ return local;
+ }
+ return false;
+ }
+ public void setThreadLocalRequestContext(boolean b) {
+ currentRequestContext.put(THREAD_LOCAL_REQUEST_CONTEXT, b);
+ }
+
+
+ public Object[] invoke(BindingOperationInfo oi, Object... params) throws Exception {
+ return invoke(oi, params, null);
+ }
+
+ public Object[] invoke(String operationName, Object... params) throws Exception {
+ QName q = new QName(getEndpoint().getService().getName().getNamespaceURI(), operationName);
+
+ return invoke(q, params);
+ }
+
+ public Object[] invoke(QName operationName, Object... params) throws Exception {
+ BindingOperationInfo op = getEndpoint().getEndpointInfo().getBinding().getOperation(operationName);
+ if (op == null) {
+ throw new UncheckedException(
+ new org.apache.cxf.common.i18n.Message("NO_OPERATION", LOG, operationName));
+ }
+
+ if (op.isUnwrappedCapable()) {
+ op = op.getUnwrappedOperation();
+ }
+
+ return invoke(op, params);
+ }
+
+ public Object[] invokeWrapped(String operationName, Object... params) throws Exception {
+ QName q = new QName(getEndpoint().getService().getName().getNamespaceURI(), operationName);
+
+ return invokeWrapped(q, params);
+ }
+
+ public Object[] invokeWrapped(QName operationName, Object... params) throws Exception {
+ BindingOperationInfo op = getEndpoint().getEndpointInfo().getBinding().getOperation(operationName);
+ if (op == null) {
+ throw new UncheckedException(
+ new org.apache.cxf.common.i18n.Message("NO_OPERATION", LOG, operationName));
+ }
+ return invoke(op, params);
+ }
+
+ public Object[] invoke(BindingOperationInfo oi,
+ Object[] params,
+ Exchange exchange) throws Exception {
+ Map<String, Object> context = new HashMap<>();
+ return invoke(oi, params, context, exchange);
+ }
+ public Object[] invoke(BindingOperationInfo oi,
+ Object[] params,
+ Map<String, Object> context) throws Exception {
+ return invoke(oi, params, context, (Exchange)null);
+ }
+
+ public void invoke(ClientCallback callback,
+ String operationName,
+ Object... params) throws Exception {
+ QName q = new QName(getEndpoint().getService().getName().getNamespaceURI(), operationName);
+ invoke(callback, q, params);
+ }
+
+ public void invoke(ClientCallback callback,
+ QName operationName,
+ Object... params) throws Exception {
+ BindingOperationInfo op = getEndpoint().getEndpointInfo().getBinding().getOperation(operationName);
+ if (op == null) {
+ throw new UncheckedException(
+ new org.apache.cxf.common.i18n.Message("NO_OPERATION", LOG, operationName));
+ }
+
+ if (op.isUnwrappedCapable()) {
+ op = op.getUnwrappedOperation();
+ }
+
+ invoke(callback, op, params);
+ }
+
+
+ public void invokeWrapped(ClientCallback callback,
+ String operationName,
+ Object... params)
+ throws Exception {
+ QName q = new QName(getEndpoint().getService().getName().getNamespaceURI(), operationName);
+ invokeWrapped(callback, q, params);
+ }
+
+ public void invokeWrapped(ClientCallback callback,
+ QName operationName,
+ Object... params)
+ throws Exception {
+ BindingOperationInfo op = getEndpoint().getEndpointInfo().getBinding().getOperation(operationName);
+ if (op == null) {
+ throw new UncheckedException(
+ new org.apache.cxf.common.i18n.Message("NO_OPERATION", LOG, operationName));
+ }
+ invoke(callback, op, params);
+ }
+
+
+ public void invoke(ClientCallback callback,
+ BindingOperationInfo oi,
+ Object... params) throws Exception {
+ invoke(callback, oi, params, null, null);
+ }
+
+ public void invoke(ClientCallback callback,
+ BindingOperationInfo oi,
+ Object[] params,
+ Map<String, Object> context) throws Exception {
+ invoke(callback, oi, params, context, null);
+ }
+
+ public void invoke(ClientCallback callback,
+ BindingOperationInfo oi,
+ Object[] params,
+ Exchange exchange) throws Exception {
+ invoke(callback, oi, params, null, exchange);
+ }
+
+ public void invoke(ClientCallback callback,
+ BindingOperationInfo oi,
+ Object[] params,
+ Map<String, Object> context,
+ Exchange exchange) throws Exception {
+ doInvoke(callback, oi, params, context, exchange);
+ }
+
+ public Object[] invoke(BindingOperationInfo oi,
+ Object[] params,
+ Map<String, Object> context,
+ Exchange exchange) throws Exception {
+ return doInvoke(null, oi, params, context, exchange);
+ }
+
+ private Object[] doInvoke(final ClientCallback callback,
+ BindingOperationInfo oi,
+ Object[] params,
+ Map<String, Object> context,
+ Exchange exchange) throws Exception {
+ Bus origBus = BusFactory.getAndSetThreadDefaultBus(bus);
+ ClassLoaderHolder origLoader = null;
+ Map<String, Object> resContext = null;
+ try {
+ ClassLoader loader = bus.getExtension(ClassLoader.class);
+ if (loader != null) {
+ origLoader = ClassLoaderUtils.setThreadContextClassloader(loader);
+ }
+ if (exchange == null) {
+ exchange = new ExchangeImpl();
+ }
+ exchange.setSynchronous(callback == null);
+ Endpoint endpoint = getEndpoint();
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Invoke, operation info: " + oi + ", params: " + Arrays.toString(params));
+ }
+ Message message = endpoint.getBinding().createMessage();
+
+ // Make sure INVOCATION CONTEXT, REQUEST_CONTEXT and RESPONSE_CONTEXT are present
+ // on message
+ if (context == null) {
+ context = new HashMap<>();
+ }
+ Map<String, Object> reqContext = CastUtils.cast((Map<?, ?>)context.get(REQUEST_CONTEXT));
+ resContext = CastUtils.cast((Map<?, ?>)context.get(RESPONSE_CONTEXT));
+ if (reqContext == null) {
+ reqContext = new HashMap<>(getRequestContext());
+ context.put(REQUEST_CONTEXT, reqContext);
+ }
+ if (resContext == null) {
+ resContext = new ResponseContext(responseContext);
+ context.put(RESPONSE_CONTEXT, resContext);
+ }
+
+ message.put(Message.INVOCATION_CONTEXT, context);
+ setContext(reqContext, message);
+ exchange.putAll(reqContext);
+
+ setParameters(params, message);
+
+ if (null != oi) {
+ exchange.setOneWay(oi.getOutput() == null);
+ }
+
+ exchange.setOutMessage(message);
+ exchange.put(ClientCallback.class, callback);
+
+ setOutMessageProperties(message, oi);
+ setExchangeProperties(exchange, endpoint, oi);
+
+ PhaseInterceptorChain chain = setupInterceptorChain(endpoint);
+ message.setInterceptorChain(chain);
+ if (callback == null) {
+ chain.setFaultObserver(outFaultObserver);
+ } else {
+ // We need to wrap the outFaultObserver if the callback is not null
+ // calling the conduitSelector.complete to make sure the fail over feature works
+ chain.setFaultObserver(new MessageObserver() {
+ public void onMessage(Message message) {
+ Exception ex = message.getContent(Exception.class);
+ if (ex != null) {
+ completeExchange(message.getExchange());
+ if (message.getContent(Exception.class) == null) {
+ // handle the right response
+ Message inMsg = message.getExchange().getInMessage();
+ Map<String, Object> ctx = responseContext.get(Thread.currentThread());
+ List<Object> resList = CastUtils.cast(inMsg.getContent(List.class));
+ Object[] result = resList == null ? null : resList.toArray();
+ callback.handleResponse(ctx, result);
+ return;
+ }
+ }
+ outFaultObserver.onMessage(message);
+ }
+ });
+ }
+ prepareConduitSelector(message);
+
+ // add additional interceptors and such
+ modifyChain(chain, message, false);
+ try {
+ chain.doIntercept(message);
+ } catch (Fault fault) {
+ enrichFault(fault);
+ throw fault;
+ }
+
+ if (callback != null) {
+ return null;
+ }
+ return processResult(message, exchange, oi, resContext);
+ } finally {
+ //ensure ResponseContext has HTTP RESPONSE CODE
+ if (null != exchange) {
+ Integer responseCode = (Integer)exchange.get(Message.RESPONSE_CODE);
+ resContext.put(MessageContext.HTTP_RESPONSE_CODE, responseCode);
+ resContext.put(org.apache.cxf.message.Message.RESPONSE_CODE, responseCode);
+ setResponseContext(resContext);
+ }
+ if (origLoader != null) {
+ origLoader.reset();
+ }
+ if (origBus != bus) {
+ BusFactory.setThreadDefaultBus(origBus);
+ }
+ }
+ }
+
+ private void completeExchange(Exchange exchange) {
+ getConduitSelector().complete(exchange);
+ }
+
+ /**
+ * TODO This is SOAP specific code and should not be in cxf core
+ * @param fault
+ */
+ private void enrichFault(Fault fault) {
+ if (fault.getCause().getCause() instanceof IOException
+ || fault.getCause() instanceof IOException) {
+ String soap11NS = "http://schemas.xmlsoap.org/soap/envelope/";
+ String soap12NS = "http://www.w3.org/2003/05/soap-envelope";
+ QName faultCode = fault.getFaultCode();
+ //for SoapFault, if it's underlying cause is IOException,
+ //it means something like server is down or can't create
+ //connection, according to soap spec we should set fault as
+ //Server Fault
+ if (faultCode.getNamespaceURI().equals(
+ soap11NS)
+ && "Client".equals(faultCode.getLocalPart())) {
+ faultCode = new QName(soap11NS, "Server");
+ fault.setFaultCode(faultCode);
+ }
+ if (faultCode.getNamespaceURI().equals(
+ soap12NS)
+ && "Sender".equals(faultCode.getLocalPart())) {
+ faultCode = new QName(soap12NS, "Receiver");
+ fault.setFaultCode(faultCode);
+ }
+ }
+ }
+
+ protected Object[] processResult(Message message,
+ Exchange exchange,
+ BindingOperationInfo oi,
+ Map<String, Object> resContext) throws Exception {
+ Exception ex = null;
+ // Check to see if there is a Fault from the outgoing chain if it's an out Message
+ if (!message.get(Message.INBOUND_MESSAGE).equals(Boolean.TRUE)) {
+ ex = message.getContent(Exception.class);
+ }
+ boolean mepCompleteCalled = false;
+ if (ex != null) {
+ completeExchange(exchange);
+ mepCompleteCalled = true;
+ if (message.getContent(Exception.class) != null) {
+ throw ex;
+ }
+ }
+ ex = message.getExchange().get(Exception.class);
+ if (ex != null) {
+ if (!mepCompleteCalled) {
+ completeExchange(exchange);
+ }
+ throw ex;
+ }
+
+ //REVISIT
+ // - use a protocol neutral no-content marker instead of 202?
+ // - move the decoupled destination property name into api
+ Integer responseCode = (Integer)exchange.get(Message.RESPONSE_CODE);
+ if (null != responseCode && 202 == responseCode) {
+ Endpoint ep = exchange.getEndpoint();
+ if (null != ep && null != ep.getEndpointInfo() && null == ep.getEndpointInfo().
+ getProperty("org.apache.cxf.ws.addressing.MAPAggregator.decoupledDestination")) {
+ return null;
+ }
+ }
+
+ // Wait for a response if we need to
+ if (oi != null && !oi.getOperationInfo().isOneWay()) {
+ waitResponse(exchange);
+ }
+
+ // leave the input stream open for the caller
+ Boolean keepConduitAlive = (Boolean)exchange.get(Client.KEEP_CONDUIT_ALIVE);
+ if (keepConduitAlive == null || !keepConduitAlive) {
+ completeExchange(exchange);
+ }
+
+ // Grab the response objects if there are any
+ List<Object> resList = null;
+ Message inMsg = exchange.getInMessage();
+ if (inMsg != null) {
+ if (null != resContext) {
+ resContext.putAll(inMsg);
+ // remove the recursive reference if present
+ resContext.remove(Message.INVOCATION_CONTEXT);
+ setResponseContext(resContext);
+ }
+ resList = CastUtils.cast(inMsg.getContent(List.class));
+ }
+
+ // check for an incoming fault
+ ex = getException(exchange);
+
+ if (ex != null) {
+ throw ex;
+ }
+
+ if (resList == null
+ && oi != null && !oi.getOperationInfo().isOneWay()) {
+
+ BindingOperationInfo boi = oi;
+ if (boi.isUnwrapped()) {
+ boi = boi.getWrappedOperation();
+ }
+ if (!boi.getOutput().getMessageParts().isEmpty()) {
+ //we were supposed to get some output, but didn't.
+ throw new IllegalEmptyResponseException("Response message did not contain proper response data."
+ + " Expected: " + boi.getOutput().getMessageParts().get(0).getConcreteName());
+ }
+ }
+ if (resList != null) {
+ return resList.toArray();
+ }
+
+ return null;
+ }
+ protected Exception getException(Exchange exchange) {
+ if (exchange.getInFaultMessage() != null) {
+ return exchange.getInFaultMessage().getContent(Exception.class);
+ } else if (exchange.getOutFaultMessage() != null) {
+ return exchange.getOutFaultMessage().getContent(Exception.class);
+ } else if (exchange.getInMessage() != null) {
+ return exchange.getInMessage().getContent(Exception.class);
+ }
+ return null;
+ }
+
+ protected void setContext(Map<String, Object> ctx, Message message) {
+ if (ctx != null) {
+ message.putAll(ctx);
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("set requestContext to message be" + ctx);
+ }
+ }
+ }
+
+ protected void waitResponse(Exchange exchange) throws IOException {
+ synchronized (exchange) {
+ long remaining = synchronousTimeout;
+ Long o = PropertyUtils.getLong(exchange.getOutMessage(), SYNC_TIMEOUT);
+ if (o != null) {
+ remaining = o;
+ }
+ while (!Boolean.TRUE.equals(exchange.get(FINISHED)) && remaining > 0) {
+ long start = System.currentTimeMillis();
+ try {
+ exchange.wait(remaining);
+ } catch (InterruptedException ex) {
+ // ignore
+ }
+ long end = System.currentTimeMillis();
+ remaining -= (int)(end - start);
+ }
+ if (!Boolean.TRUE.equals(exchange.get(FINISHED))) {
+ LogUtils.log(LOG, Level.WARNING, "RESPONSE_TIMEOUT",
+ exchange.getBindingOperationInfo().getOperationInfo().getName().toString());
+ String msg = new org.apache.cxf.common.i18n.Message("RESPONSE_TIMEOUT", LOG, exchange
+ .getBindingOperationInfo().getOperationInfo().getName().toString()).toString();
+ throw new IOException(msg);
+ }
+ }
+ }
+
+ protected void setParameters(Object[] params, Message message) {
+ MessageContentsList contents = new MessageContentsList(params);
+ message.setContent(List.class, contents);
+ }
+
+ public void onMessage(Message message) {
+ if (bus == null) {
+ throw new IllegalStateException("Message received on a Client that has been closed or destroyed.");
+ }
+ Endpoint endpoint = message.getExchange().getEndpoint();
+ if (endpoint == null) {
+ // in this case correlation will occur outside the transport,
+ // however there's a possibility that the endpoint may have been
+ // rebased in the meantime, so that the response will be mediated
+ // via a set of in interceptors provided by a *different* endpoint
+ //
+ endpoint = getConduitSelector().getEndpoint();
+ message.getExchange().put(Endpoint.class, endpoint);
+ }
+ message = endpoint.getBinding().createMessage(message);
+ message.getExchange().setInMessage(message);
+ message.put(Message.REQUESTOR_ROLE, Boolean.TRUE);
+ message.put(Message.INBOUND_MESSAGE, Boolean.TRUE);
+ PhaseManager pm = bus.getExtension(PhaseManager.class);
+
+ List<Interceptor<? extends Message>> i1 = bus.getInInterceptors();
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Interceptors contributed by bus: " + i1);
+ }
+ List<Interceptor<? extends Message>> i2 = getInInterceptors();
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Interceptors contributed by client: " + i2);
+ }
+ List<Interceptor<? extends Message>> i3 = endpoint.getInInterceptors();
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Interceptors contributed by endpoint: " + i3);
+ }
+ List<Interceptor<? extends Message>> i4 = endpoint.getBinding().getInInterceptors();
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Interceptors contributed by binding: " + i4);
+ }
+
+ PhaseInterceptorChain chain;
+ if (endpoint.getService().getDataBinding() instanceof InterceptorProvider) {
+ InterceptorProvider p = (InterceptorProvider)endpoint.getService().getDataBinding();
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Interceptors contributed by databinding: " + p.getInInterceptors());
+ }
+ chain = inboundChainCache.get(pm.getInPhases(), i1, i2, i3, i4,
+ p.getInInterceptors());
+ } else {
+ chain = inboundChainCache.get(pm.getInPhases(), i1, i2, i3, i4);
+ }
+ message.setInterceptorChain(chain);
+
+ chain.setFaultObserver(outFaultObserver);
+ modifyChain(chain, message, true);
+ modifyChain(chain, message.getExchange().getOutMessage(), true);
+
+ Bus origBus = BusFactory.getAndSetThreadDefaultBus(bus);
+ // execute chain
+ ClientCallback callback = message.getExchange().get(ClientCallback.class);
+ try {
+ if (callback != null) {
+ if (callback.isCancelled()) {
+ completeExchange(message.getExchange());
+ return;
+ }
+ callback.start(message);
+ }
+
+ String startingAfterInterceptorID = (String) message.get(
+ InterceptorChain.STARTING_AFTER_INTERCEPTOR_ID);
+ String startingInterceptorID = (String) message.get(
+ InterceptorChain.STARTING_AT_INTERCEPTOR_ID);
+ if (startingAfterInterceptorID != null) {
+ chain.doInterceptStartingAfter(message, startingAfterInterceptorID);
+ } else if (startingInterceptorID != null) {
+ chain.doInterceptStartingAt(message, startingInterceptorID);
+ } else if (message.getContent(Exception.class) != null) {
+ outFaultObserver.onMessage(message);
+ } else {
+ callback = message.getExchange().get(ClientCallback.class);
+
+ if (callback != null && !isPartialResponse(message)) {
+ try {
+ chain.doIntercept(message);
+ } catch (Throwable error) {
+ //so that asyn callback handler get chance to
+ //handle non-runtime exceptions
+ message.getExchange().setInMessage(message);
+ Map<String, Object> resCtx = CastUtils
+ .cast((Map<?, ?>) message.getExchange()
+ .getOutMessage().get(
+ Message.INVOCATION_CONTEXT));
+ resCtx = CastUtils.cast((Map<?, ?>) resCtx
+ .get(RESPONSE_CONTEXT));
+ if (resCtx != null) {
+ setResponseContext(resCtx);
+ }
+ // remove callback so that it won't be invoked twice
+ callback = message.getExchange().remove(ClientCallback.class);
+ if (callback != null) {
+ callback.handleException(resCtx, error);
+ }
+ }
+ } else {
+ chain.doIntercept(message);
+ }
+
+ }
+
+ callback = message.getExchange().get(ClientCallback.class);
+ if (callback == null || isPartialResponse(message)) {
+ return;
+ }
+
+ // remove callback so that it won't be invoked twice
+ callback = message.getExchange().remove(ClientCallback.class);
+ if (callback != null) {
+ message.getExchange().setInMessage(message);
+ Map<String, Object> resCtx = CastUtils.cast((Map<?, ?>)message
+ .getExchange()
+ .getOutMessage()
+ .get(Message.INVOCATION_CONTEXT));
+ resCtx = CastUtils.cast((Map<?, ?>)resCtx.get(RESPONSE_CONTEXT));
+ if (resCtx != null && responseContext != null) {
+ setResponseContext(resCtx);
+ }
+ try {
+ Object[] obj = processResult(message, message.getExchange(),
+ null, resCtx);
+
+ callback.handleResponse(resCtx, obj);
+ } catch (Throwable ex) {
+ callback.handleException(resCtx, ex);
+ }
+ }
+ } finally {
+ if (origBus != bus) {
+ BusFactory.setThreadDefaultBus(origBus);
+ }
+ synchronized (message.getExchange()) {
+ if (!isPartialResponse(message)
+ || message.getContent(Exception.class) != null) {
+ message.getExchange().put(FINISHED, Boolean.TRUE);
+ message.getExchange().setInMessage(message);
+ message.getExchange().notifyAll();
+ }
+ }
+ }
+ }
+
+ public Conduit getConduit() {
+ Message message = new MessageImpl();
+ Exchange exchange = new ExchangeImpl();
+ message.setExchange(exchange);
+ message.putAll(getRequestContext());
+ setExchangeProperties(exchange, getEndpoint(), null);
+ return getConduitSelector().selectConduit(message);
+ }
+
+ protected void prepareConduitSelector(Message message) {
+ getConduitSelector().prepare(message);
+ message.getExchange().put(ConduitSelector.class, getConduitSelector());
+ }
+
+ protected void setOutMessageProperties(Message message, BindingOperationInfo boi) {
+ message.put(Message.REQUESTOR_ROLE, Boolean.TRUE);
+ message.put(Message.INBOUND_MESSAGE, Boolean.FALSE);
+ if (null != boi) {
+ message.put(BindingMessageInfo.class, boi.getInput());
+ message.put(MessageInfo.class, boi.getOperationInfo().getInput());
+ }
+ }
+
+ protected void setExchangeProperties(Exchange exchange,
+ Endpoint endpoint,
+ BindingOperationInfo boi) {
+ if (endpoint != null) {
+ exchange.put(Endpoint.class, endpoint);
+ exchange.put(Service.class, endpoint.getService());
+ exchange.put(Binding.class, endpoint.getBinding());
+ }
+ if (boi != null) {
+ exchange.put(BindingOperationInfo.class, boi);
+ }
+
+ if (exchange.isSynchronous() || executor == null) {
+ exchange.put(MessageObserver.class, this);
+ } else {
+ exchange.put(Executor.class, executor);
+ exchange.put(MessageObserver.class, new MessageObserver() {
+ public void onMessage(final Message message) {
+ if (!message.getExchange()
+ .containsKey(Executor.class.getName() + ".USING_SPECIFIED")) {
+
+ executor.execute(new Runnable() {
+ public void run() {
+ ClientImpl.this.onMessage(message);
+ }
+ });
+ } else {
+ ClientImpl.this.onMessage(message);
+ }
+ }
+ });
+ }
+ exchange.put(Retryable.class, this);
+ exchange.put(Client.class, this);
+ exchange.put(Bus.class, bus);
+
+ if (endpoint != null) {
+ EndpointInfo endpointInfo = endpoint.getEndpointInfo();
+ if (boi != null) {
+ exchange.put(Message.WSDL_OPERATION, boi.getName());
+ }
+
+ QName serviceQName = endpointInfo.getService().getName();
+ exchange.put(Message.WSDL_SERVICE, serviceQName);
+
+ QName interfaceQName = endpointInfo.getService().getInterface().getName();
+ exchange.put(Message.WSDL_INTERFACE, interfaceQName);
+
+ QName portQName = endpointInfo.getName();
+ exchange.put(Message.WSDL_PORT, portQName);
+ URI wsdlDescription = endpointInfo.getProperty("URI", URI.class);
+ if (wsdlDescription == null) {
+ String address = endpointInfo.getAddress();
+ try {
+ wsdlDescription = new URI(address + "?wsdl");
+ } catch (URISyntaxException e) {
+ // do nothing
+ }
+ endpointInfo.setProperty("URI", wsdlDescription);
+ }
+ exchange.put(Message.WSDL_DESCRIPTION, wsdlDescription);
+ }
+ }
+
+ protected PhaseInterceptorChain setupInterceptorChain(Endpoint endpoint) {
+
+ PhaseManager pm = bus.getExtension(PhaseManager.class);
+
+ List<Interceptor<? extends Message>> i1 = bus.getOutInterceptors();
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Interceptors contributed by bus: " + i1);
+ }
+ List<Interceptor<? extends Message>> i2 = getOutInterceptors();
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Interceptors contributed by client: " + i2);
+ }
+ List<Interceptor<? extends Message>> i3 = endpoint.getOutInterceptors();
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Interceptors contributed by endpoint: " + i3);
+ }
+ List<Interceptor<? extends Message>> i4 = endpoint.getBinding().getOutInterceptors();
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Interceptors contributed by binding: " + i4);
+ }
+ List<Interceptor<? extends Message>> i5 = null;
+ if (endpoint.getService().getDataBinding() instanceof InterceptorProvider) {
+ i5 = ((InterceptorProvider)endpoint.getService().getDataBinding()).getOutInterceptors();
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Interceptors contributed by databinding: " + i5);
+ }
+ }
+ if (i5 != null) {
+ return outboundChainCache.get(pm.getOutPhases(), i1, i2, i3, i4, i5);
+ }
+ return outboundChainCache.get(pm.getOutPhases(), i1, i2, i3, i4);
+ }
+
+ protected void modifyChain(InterceptorChain chain, Message ctx, boolean in) {
+ if (ctx == null) {
+ return;
+ }
+ Collection<InterceptorProvider> providers
+ = CastUtils.cast((Collection<?>)ctx.get(Message.INTERCEPTOR_PROVIDERS));
+ if (providers != null) {
+ for (InterceptorProvider p : providers) {
+ if (in) {
+ chain.add(p.getInInterceptors());
+ } else {
+ chain.add(p.getOutInterceptors());
+ }
+ }
+ }
+ String key = in ? Message.IN_INTERCEPTORS : Message.OUT_INTERCEPTORS;
+ Collection<Interceptor<? extends Message>> is
+ = CastUtils.cast((Collection<?>)ctx.get(key));
+ if (is != null) {
+ chain.add(is);
+ }
+ }
+
+ protected void setEndpoint(Endpoint e) {
+ getConduitSelector().setEndpoint(e);
+ }
+
+ public int getSynchronousTimeout() {
+ return synchronousTimeout;
+ }
+
+ public void setSynchronousTimeout(int synchronousTimeout) {
+ this.synchronousTimeout = synchronousTimeout;
+ }
+
+ public final ConduitSelector getConduitSelector() {
+ return getConduitSelector(null);
+ }
+
+ protected final ConduitSelector getConduitSelector(
+ ConduitSelector override
+ ) {
+ if (null == conduitSelector) {
+ setConduitSelector(override);
+ }
+ return conduitSelector;
+ }
+
+ public final synchronized void setConduitSelector(ConduitSelector selector) {
+ conduitSelector = selector == null ? new UpfrontConduitSelector() : selector;
+ }
+
+ private boolean isPartialResponse(Message in) {
+ return Boolean.TRUE.equals(in.get(Message.PARTIAL_RESPONSE_MESSAGE));
+ }
+
+ @Override
+ public void close() throws Exception {
+ destroy();
+ }
+
+
+ public class EchoContext extends ConcurrentHashMap<String, Object> {
+ private static final long serialVersionUID = 1L;
+ public EchoContext(Map<String, Object> sharedMap) {
+ super(8, 0.75f, 4);
+ putAll(sharedMap);
+ }
+
+ public void reload() {
+ super.clear();
+ super.putAll(requestContext.get(latestContextThread));
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ try {
+ for (Map.Entry<Thread, EchoContext> ent : requestContext.entrySet()) {
+ if (ent.getValue() == this) {
+ requestContext.remove(ent.getKey());
+ return;
+ }
+ }
+ } catch (Throwable t) {
+ //ignore
+ }
+ }
+ }
+
+ /**
+ * Class to handle the response contexts. The clear is overloaded to remove
+ * this context from the threadLocal caches in the ClientImpl
+ */
+ static class ResponseContext implements Map<String, Object>, Serializable {
+ private static final long serialVersionUID = 2L;
+ final Map<String, Object> wrapped;
+ final Map<Thread, ResponseContext> responseContext;
+
+ ResponseContext(Map<String, Object> origMap, Map<Thread, ResponseContext> rc) {
+ wrapped = origMap;
+ responseContext = rc;
+ }
+
+ ResponseContext(Map<Thread, ResponseContext> rc) {
+ wrapped = new HashMap<>();
+ responseContext = rc;
+ }
+
+ @Override
+ public void clear() {
+ wrapped.clear();
+ try {
+ for (Map.Entry<Thread, ResponseContext> ent : responseContext.entrySet()) {
+ if (ent.getValue() == this) {
+ responseContext.remove(ent.getKey());
+ return;
+ }
+ }
+ } catch (Throwable t) {
+ //ignore
+ }
+ }
+
+ @Override
+ public int size() {
+ return wrapped.size();
+ }
+ @Override
+ public boolean isEmpty() {
+ return wrapped.isEmpty();
+ }
+ @Override
+ public boolean containsKey(Object key) {
+ return wrapped.containsKey(key);
+ }
+ @Override
+ public boolean containsValue(Object value) {
+ return wrapped.containsKey(value);
+ }
+ @Override
+ public Object get(Object key) {
+ return wrapped.get(key);
+ }
+ @Override
+ public Object put(String key, Object value) {
+ return wrapped.put(key, value);
+ }
+ @Override
+ public Object remove(Object key) {
+ return wrapped.remove(key);
+ }
+ @Override
+ public void putAll(Map<? extends String, ? extends Object> m) {
+ wrapped.putAll(m);
+ }
+ @Override
+ public Set<String> keySet() {
+ return wrapped.keySet();
+ }
+ @Override
+ public Collection<Object> values() {
+ return wrapped.values();
+ }
+ @Override
+ public Set<Entry<String, Object>> entrySet() {
+ return wrapped.entrySet();
+ }
+ }
+
+ public void setExecutor(Executor executor) {
+ if (!SynchronousExecutor.isA(executor)) {
+ this.executor = executor;
+ }
+ }
+
+
+ public class IllegalEmptyResponseException extends IllegalStateException {
+ private static final long serialVersionUID = 1L;
+
+ public IllegalEmptyResponseException() {
+ super();
+ }
+
+ public IllegalEmptyResponseException(String message) {
+ super(message);
+ }
+
+ public IllegalEmptyResponseException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public IllegalEmptyResponseException(Throwable cause) {
+ super(cause);
+ }
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/endpoint/EndpointImpl.java b/transform/src/patch/java/org/apache/cxf/endpoint/EndpointImpl.java
new file mode 100644
index 0000000..340211b
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/endpoint/EndpointImpl.java
@@ -0,0 +1,220 @@
+/**
+ * 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.endpoint;
+
+import java.io.Closeable;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+import java.util.logging.Logger;
+
+import javax.xml.namespace.QName;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusException;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.binding.Binding;
+import org.apache.cxf.binding.BindingFactory;
+import org.apache.cxf.binding.BindingFactoryManager;
+import org.apache.cxf.common.i18n.Message;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.configuration.Configurable;
+import org.apache.cxf.feature.Feature;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.interceptor.AbstractAttributedInterceptorProvider;
+import org.apache.cxf.interceptor.ClientFaultConverter;
+import org.apache.cxf.interceptor.InFaultChainInitiatorObserver;
+import org.apache.cxf.interceptor.MessageSenderInterceptor;
+import org.apache.cxf.interceptor.OutFaultChainInitiatorObserver;
+import org.apache.cxf.service.Service;
+import org.apache.cxf.service.model.BindingInfo;
+import org.apache.cxf.service.model.EndpointInfo;
+import org.apache.cxf.transport.MessageObserver;
+
+public class EndpointImpl extends AbstractAttributedInterceptorProvider implements Endpoint, Configurable {
+
+ private static final long serialVersionUID = -7660560719050162091L;
+ private static final Logger LOG = LogUtils.getL7dLogger(EndpointImpl.class);
+ private static final ResourceBundle BUNDLE = LOG.getResourceBundle();
+
+ private Service service;
+ private Binding binding;
+ private EndpointInfo endpointInfo;
+ private Executor executor;
+ private Bus bus;
+ private MessageObserver inFaultObserver;
+ private MessageObserver outFaultObserver;
+ private List<Feature> activeFeatures;
+ private List<Closeable> cleanupHooks;
+
+ public EndpointImpl(Bus bus, Service s, QName endpointName) throws EndpointException {
+ this(bus, s, s.getEndpointInfo(endpointName));
+ }
+
+ public EndpointImpl(Bus bus, Service s, EndpointInfo ei) throws EndpointException {
+ if (ei == null) {
+ throw new NullPointerException("EndpointInfo can not be null!");
+ }
+
+ if (bus == null) {
+ this.bus = BusFactory.getThreadDefaultBus();
+ } else {
+ this.bus = bus;
+ }
+ service = s;
+ endpointInfo = ei;
+
+ createBinding(endpointInfo.getBinding());
+
+ inFaultObserver = new InFaultChainInitiatorObserver(bus);
+ outFaultObserver = new OutFaultChainInitiatorObserver(bus);
+
+ getInFaultInterceptors().add(new ClientFaultConverter());
+ getOutInterceptors().add(new MessageSenderInterceptor());
+ getOutFaultInterceptors().add(new MessageSenderInterceptor());
+ }
+
+ public String getBeanName() {
+ return endpointInfo.getName().toString() + ".endpoint";
+ }
+
+
+ public EndpointInfo getEndpointInfo() {
+ return endpointInfo;
+ }
+
+ public Service getService() {
+ return service;
+ }
+
+ public Binding getBinding() {
+ return binding;
+ }
+
+ public Executor getExecutor() {
+ return executor == null ? service.getExecutor() : executor;
+ }
+
+ public void setExecutor(Executor e) {
+ executor = e;
+ }
+
+ public Bus getBus() {
+ return bus;
+ }
+
+ public void setBus(Bus bus) {
+ this.bus = bus;
+ }
+
+ final void createBinding(BindingInfo bi) throws EndpointException {
+ if (null != bi) {
+ String namespace = bi.getBindingId();
+ try {
+ final BindingFactory bf = bus.getExtension(BindingFactoryManager.class).getBindingFactory(namespace);
+ if (null == bf) {
+ Message msg = new Message("NO_BINDING_FACTORY", BUNDLE, namespace);
+ throw new EndpointException(msg);
+ }
+ binding = bf.createBinding(bi);
+ } catch (BusException ex) {
+ throw new EndpointException(ex);
+ }
+ }
+ }
+
+
+ public MessageObserver getInFaultObserver() {
+ return inFaultObserver;
+ }
+
+ public MessageObserver getOutFaultObserver() {
+ return outFaultObserver;
+ }
+
+ public void setInFaultObserver(MessageObserver observer) {
+ inFaultObserver = observer;
+ }
+
+ public void setOutFaultObserver(MessageObserver observer) {
+ outFaultObserver = observer;
+
+ }
+
+ /**
+ * Utility method to make it easy to set properties from Spring.
+ *
+ * @param properties
+ */
+ public void setProperties(Map<String, Object> properties) {
+ this.putAll(properties);
+ }
+
+ /**
+ * @return the list of features <b>already</b> activated for this endpoint.
+ */
+ public List<Feature> getActiveFeatures() {
+ return activeFeatures;
+ }
+
+ /**
+ * @param features the list of features <b>already</b> activated for this endpoint.
+ */
+ public void initializeActiveFeatures(List<? extends Feature> features) {
+ activeFeatures = CastUtils.cast(features);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof EndpointImpl)) {
+ return false;
+ }
+
+ return super.equals(obj);
+ }
+
+ /**
+ * Returns the hashCode based on the EndpointInfo so that this object
+ * can be used as a map key.
+ */
+ @Override
+ public int hashCode() {
+ return endpointInfo.hashCode();
+ }
+
+ public synchronized void addCleanupHook(Closeable c) {
+ if (cleanupHooks == null) {
+ cleanupHooks = new CopyOnWriteArrayList<>();
+ }
+ cleanupHooks.add(c);
+ }
+ public List<Closeable> getCleanupHooks() {
+ if (cleanupHooks == null) {
+ return Collections.emptyList();
+ }
+ return cleanupHooks;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/endpoint/ServerImpl.java b/transform/src/patch/java/org/apache/cxf/endpoint/ServerImpl.java
new file mode 100644
index 0000000..c417cb6
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/endpoint/ServerImpl.java
@@ -0,0 +1,221 @@
+/**
+ * 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.endpoint;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.management.JMException;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusException;
+import org.apache.cxf.binding.BindingFactory;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.logging.RegexLoggingFilter;
+import org.apache.cxf.management.InstrumentationManager;
+import org.apache.cxf.service.model.EndpointInfo;
+import org.apache.cxf.transport.Destination;
+import org.apache.cxf.transport.DestinationFactory;
+import org.apache.cxf.transport.DestinationFactoryManager;
+import org.apache.cxf.transport.MessageObserver;
+import org.apache.cxf.transport.MultipleEndpointObserver;
+
+public class ServerImpl implements Server {
+ private static final Logger LOG = LogUtils.getL7dLogger(ServerImpl.class);
+
+ protected final Endpoint endpoint;
+ protected final Bus bus;
+ protected final BindingFactory bindingFactory;
+
+ private Destination destination;
+ private ServerRegistry serverRegistry;
+ private ServerLifeCycleManager slcMgr;
+ private InstrumentationManager iMgr;
+ private ManagedEndpoint mep;
+ private boolean stopped = true;
+ private boolean destroyDest = true;
+
+ public ServerImpl(Bus bus,
+ Endpoint endpoint,
+ DestinationFactory destinationFactory,
+ BindingFactory bindingFactory) throws BusException, IOException {
+ this.endpoint = endpoint;
+ this.bus = bus;
+ this.bindingFactory = bindingFactory;
+
+ initDestination(destinationFactory);
+ }
+
+ private void initDestination(DestinationFactory destinationFactory) throws BusException, IOException {
+ EndpointInfo ei = endpoint.getEndpointInfo();
+
+ //Treat local transport as a special case, transports loaded by transportId can be replaced
+ //by local transport when the publishing address is a local transport protocol.
+ //Of course its not an ideal situation here to use a hard-coded prefix. To be refactored.
+ if (destinationFactory == null) {
+ if (ei.getAddress() != null && ei.getAddress().indexOf("local://") != -1) {
+ destinationFactory = bus.getExtension(DestinationFactoryManager.class)
+ .getDestinationFactoryForUri(ei.getAddress());
+ }
+
+ if (destinationFactory == null) {
+ destinationFactory = bus.getExtension(DestinationFactoryManager.class)
+ .getDestinationFactory(ei.getTransportId());
+ }
+ }
+
+ destination = destinationFactory.getDestination(ei, bus);
+ String wantFilter = ei.getAddress();
+
+ if (wantFilter != null && wantFilter.startsWith("jms")) {
+ RegexLoggingFilter filter = new RegexLoggingFilter();
+ filter.setPattern("jms(.*?)password=+([^ ]+)");
+ filter.setGroup(2);
+ wantFilter = filter.filter(wantFilter).toString();
+ }
+ LOG.info("Setting the server's publish address to be " + wantFilter);
+ serverRegistry = bus.getExtension(ServerRegistry.class);
+
+ mep = new ManagedEndpoint(bus, endpoint, this);
+
+ slcMgr = bus.getExtension(ServerLifeCycleManager.class);
+ if (slcMgr != null) {
+ slcMgr.registerListener(mep);
+ }
+
+ iMgr = bus.getExtension(InstrumentationManager.class);
+ if (iMgr != null) {
+ try {
+ iMgr.register(mep);
+ } catch (JMException jmex) {
+ LOG.log(Level.WARNING, "Registering ManagedEndpoint failed.", jmex);
+ }
+ }
+ }
+
+ public Destination getDestination() {
+ return destination;
+ }
+
+ public void setDestination(Destination destination) {
+ this.destination = destination;
+ }
+
+ public void start() {
+ if (!stopped) {
+ return;
+ }
+ LOG.fine("Server is starting.");
+
+ try {
+ bindingFactory.addListener(destination, endpoint);
+ } catch (RuntimeException e) {
+ if (e.getMessage().contains("endpoint already registered on address")) {
+ //this destination is used by another endpoint with same endpoint address
+ //so shouldn't be destroyed by this server
+ this.destroyDest = false;
+ }
+ throw e;
+ }
+
+ // register the active server to run
+ if (null != serverRegistry) {
+ LOG.fine("register the server to serverRegistry ");
+ serverRegistry.register(this);
+ }
+ if (slcMgr == null) {
+ slcMgr = bus.getExtension(ServerLifeCycleManager.class);
+ if (slcMgr != null && mep != null) {
+ slcMgr.registerListener(mep);
+ }
+ }
+ if (slcMgr != null) {
+ slcMgr.startServer(this);
+ }
+ stopped = false;
+ }
+
+ public boolean isStopped() {
+ return stopped;
+ }
+ public boolean isStarted() {
+ return !stopped;
+ }
+
+ public void stop() {
+ if (stopped) {
+ return;
+ }
+
+ LOG.fine("Server is stopping.");
+
+ for (Closeable c : endpoint.getCleanupHooks()) {
+ try {
+ c.close();
+ } catch (IOException e) {
+ //ignore
+ }
+ }
+ if (slcMgr != null) {
+ slcMgr.stopServer(this);
+ }
+
+ MessageObserver mo = getDestination().getMessageObserver();
+ if (mo instanceof MultipleEndpointObserver) {
+ ((MultipleEndpointObserver)mo).getEndpoints().remove(endpoint);
+ if (((MultipleEndpointObserver)mo).getEndpoints().isEmpty()) {
+ getDestination().setMessageObserver(null);
+ }
+ } else {
+ getDestination().setMessageObserver(null);
+ }
+ stopped = true;
+ }
+
+ public void destroy() {
+ stop();
+ if (this.destroyDest) {
+ //we should shutdown the destination here
+ getDestination().shutdown();
+ }
+
+ if (null != serverRegistry) {
+ LOG.fine("unregister the server to serverRegistry ");
+ serverRegistry.unregister(this);
+ }
+
+ if (iMgr != null) {
+ try {
+ iMgr.unregister(mep);
+ } catch (JMException jmex) {
+ LOG.log(Level.WARNING, "Unregistering ManagedEndpoint failed.", jmex);
+ }
+ iMgr = null;
+ }
+
+ }
+
+ public Endpoint getEndpoint() {
+ return endpoint;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/feature/FastInfosetFeature.java b/transform/src/patch/java/org/apache/cxf/feature/FastInfosetFeature.java
new file mode 100644
index 0000000..daa4bd3
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/feature/FastInfosetFeature.java
@@ -0,0 +1,110 @@
+/**
+ * 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.feature;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.common.injection.NoJSR250Annotations;
+import org.apache.cxf.interceptor.FIStaxInInterceptor;
+import org.apache.cxf.interceptor.FIStaxOutInterceptor;
+import org.apache.cxf.interceptor.InterceptorProvider;
+
+
+/**
+ * <pre>
+ * <![CDATA[
+ <jaxws:endpoint ...>
+ <jaxws:features>
+ <bean class="org.apache.cxf.feature.FastInfosetFeature"/>
+ </jaxws:features>
+ </jaxws:endpoint>
+ ]]>
+ </pre>
+ */
+@NoJSR250Annotations
+public class FastInfosetFeature extends DelegatingFeature<FastInfosetFeature.Portable> {
+ public FastInfosetFeature() {
+ super(new Portable());
+ }
+
+ public void setForce(boolean b) {
+ delegate.setForce(b);
+ }
+
+ public boolean getForce() {
+ return delegate.getForce();
+ }
+
+ public static class Portable implements AbstractPortableFeature {
+ boolean force;
+ private Integer serializerAttributeValueMapMemoryLimit;
+ private Integer serializerMinAttributeValueSize;
+ private Integer serializerMaxAttributeValueSize;
+ private Integer serializerCharacterContentChunkMapMemoryLimit;
+ private Integer serializerMinCharacterContentChunkSize;
+ private Integer serializerMaxCharacterContentChunkSize;
+
+ @Override
+ public void doInitializeProvider(InterceptorProvider provider, Bus bus) {
+
+ FIStaxInInterceptor in = new FIStaxInInterceptor();
+
+ FIStaxOutInterceptor out = new FIStaxOutInterceptor(force);
+ if (serializerAttributeValueMapMemoryLimit != null && serializerAttributeValueMapMemoryLimit > 0) {
+ out.setSerializerAttributeValueMapMemoryLimit(serializerAttributeValueMapMemoryLimit);
+ }
+ if (serializerMinAttributeValueSize != null && serializerMinAttributeValueSize > 0) {
+ out.setSerializerMinAttributeValueSize(serializerMinAttributeValueSize);
+ }
+ if (serializerMaxAttributeValueSize != null && serializerMaxAttributeValueSize > 0) {
+ out.setSerializerMaxAttributeValueSize(serializerMaxAttributeValueSize);
+ }
+ if (serializerCharacterContentChunkMapMemoryLimit != null
+ && serializerCharacterContentChunkMapMemoryLimit > 0) {
+ out.setSerializerCharacterContentChunkMapMemoryLimit(
+ serializerCharacterContentChunkMapMemoryLimit);
+ }
+ if (serializerMinCharacterContentChunkSize != null && serializerMinCharacterContentChunkSize > 0) {
+ out.setSerializerMinCharacterContentChunkSize(serializerMinCharacterContentChunkSize);
+ }
+ if (serializerMaxCharacterContentChunkSize != null && serializerMaxCharacterContentChunkSize > 0) {
+ out.setSerializerMaxCharacterContentChunkSize(serializerMaxCharacterContentChunkSize);
+ }
+
+ provider.getInInterceptors().add(in);
+ provider.getInFaultInterceptors().add(in);
+ provider.getOutInterceptors().add(out);
+ provider.getOutFaultInterceptors().add(out);
+ }
+
+ /**
+ * Set if FastInfoset is always used without negotiation
+ * @param b
+ */
+ public void setForce(boolean b) {
+ force = b;
+ }
+
+ /**
+ * Retrieve the value set with {@link #setForce(boolean)}.
+ */
+ public boolean getForce() {
+ return force;
+ }
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/feature/WrappedFeature.java b/transform/src/patch/java/org/apache/cxf/feature/WrappedFeature.java
new file mode 100644
index 0000000..2236a38
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/feature/WrappedFeature.java
@@ -0,0 +1,61 @@
+/**
+ * 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.feature;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.endpoint.Client;
+import org.apache.cxf.endpoint.Server;
+import org.apache.cxf.interceptor.InterceptorProvider;
+
+/**
+ * A Feature is something that is able to customize a Server, Client, or Bus, typically
+ * adding capabilities. For instance, there may be a LoggingFeature which configures
+ * one of the above to log each of their messages.
+ * <p>
+ * By default the initialize methods all delegate to initializeProvider(InterceptorProvider).
+ * If you're simply adding interceptors to a Server, Client, or Bus, this allows you to add
+ * them easily.
+ */
+public class WrappedFeature extends AbstractFeature {
+ final Feature wrapped;
+ public WrappedFeature(Feature f) {
+ wrapped = f;
+ }
+
+ @Override
+ public void initialize(Server server, Bus bus) {
+ wrapped.initialize(server, bus);
+ }
+
+ @Override
+ public void initialize(Client client, Bus bus) {
+ wrapped.initialize(client, bus);
+ }
+
+ @Override
+ public void initialize(InterceptorProvider interceptorProvider, Bus bus) {
+ wrapped.initialize(interceptorProvider, bus);
+ }
+
+ @Override
+ public void initialize(Bus bus) {
+ wrapped.initialize(bus);
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/feature/transform/XSLTOutInterceptor.java b/transform/src/patch/java/org/apache/cxf/feature/transform/XSLTOutInterceptor.java
new file mode 100644
index 0000000..53605cc
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/feature/transform/XSLTOutInterceptor.java
@@ -0,0 +1,210 @@
+/**
+ * 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.feature.transform;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.logging.Logger;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.Templates;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.interceptor.AbstractOutDatabindingInterceptor;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.interceptor.StaxOutInterceptor;
+import org.apache.cxf.io.CachedOutputStream;
+import org.apache.cxf.io.CachedOutputStreamCallback;
+import org.apache.cxf.io.CachedWriter;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.Phase;
+import org.apache.cxf.staxutils.DelegatingXMLStreamWriter;
+import org.apache.cxf.staxutils.StaxUtils;
+
+/** Class provides XSLT transformation of outgoing message.
+ * Actually it breaks streaming (can be fixed in further versions when XSLT engine supports XML stream)
+ */
+public class XSLTOutInterceptor extends AbstractXSLTInterceptor {
+ private static final Logger LOG = LogUtils.getL7dLogger(XSLTOutInterceptor.class);
+
+ public XSLTOutInterceptor(String xsltPath) {
+ super(Phase.PRE_STREAM, StaxOutInterceptor.class, null, xsltPath);
+ }
+
+ public XSLTOutInterceptor(String phase, Class<?> before, Class<?> after, String xsltPath) {
+ super(phase, before, after, xsltPath);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ if (checkContextProperty(message)) {
+ return;
+ }
+
+ // 1. Try to get and transform XMLStreamWriter message content
+ XMLStreamWriter xWriter = message.getContent(XMLStreamWriter.class);
+ if (xWriter != null) {
+ transformXWriter(message, xWriter);
+ } else {
+ // 2. Try to get and transform OutputStream message content
+ OutputStream out = message.getContent(OutputStream.class);
+ if (out != null) {
+ transformOS(message, out);
+ } else {
+ // 3. Try to get and transform Writer message content (actually used for JMS TextMessage)
+ Writer writer = message.getContent(Writer.class);
+ if (writer != null) {
+ transformWriter(message, writer);
+ }
+ }
+ }
+ }
+
+ protected void transformXWriter(Message message, XMLStreamWriter xWriter) {
+ CachedWriter writer = new CachedWriter();
+ XMLStreamWriter delegate = StaxUtils.createXMLStreamWriter(writer);
+ XSLTStreamWriter wrapper = new XSLTStreamWriter(getXSLTTemplate(), writer, delegate, xWriter);
+ message.setContent(XMLStreamWriter.class, wrapper);
+ message.put(AbstractOutDatabindingInterceptor.DISABLE_OUTPUTSTREAM_OPTIMIZATION,
+ Boolean.TRUE);
+ }
+
+ protected void transformOS(Message message, OutputStream out) {
+ CachedOutputStream wrapper = new CachedOutputStream();
+ CachedOutputStreamCallback callback = new XSLTCachedOutputStreamCallback(getXSLTTemplate(), out);
+ wrapper.registerCallback(callback);
+ message.setContent(OutputStream.class, wrapper);
+ }
+
+ protected void transformWriter(Message message, Writer writer) {
+ XSLTCachedWriter wrapper = new XSLTCachedWriter(getXSLTTemplate(), writer);
+ message.setContent(Writer.class, wrapper);
+ }
+
+
+ public static class XSLTStreamWriter extends DelegatingXMLStreamWriter {
+ private final Templates xsltTemplate;
+ private final CachedWriter cachedWriter;
+ private final XMLStreamWriter origXWriter;
+
+ public XSLTStreamWriter(Templates xsltTemplate, CachedWriter cachedWriter,
+ XMLStreamWriter delegateXWriter, XMLStreamWriter origXWriter) {
+ super(delegateXWriter);
+ this.xsltTemplate = xsltTemplate;
+ this.cachedWriter = cachedWriter;
+ this.origXWriter = origXWriter;
+ }
+
+ @Override
+ public void close() {
+ Reader transformedReader = null;
+ try {
+ super.flush();
+ transformedReader = XSLTUtils.transform(xsltTemplate, cachedWriter.getReader());
+ StaxUtils.copy(new StreamSource(transformedReader), origXWriter);
+ } catch (XMLStreamException e) {
+ throw new Fault("STAX_COPY", LOG, e, e.getMessage());
+ } catch (IOException e) {
+ throw new Fault("GET_CACHED_INPUT_STREAM", LOG, e, e.getMessage());
+ } finally {
+ try {
+ if (transformedReader != null) {
+ transformedReader.close();
+ }
+ cachedWriter.close();
+ StaxUtils.close(origXWriter);
+ super.close();
+ } catch (Exception e) {
+ LOG.warning("Cannot close stream after transformation: " + e.getMessage());
+ }
+ }
+ }
+ }
+
+ public static class XSLTCachedOutputStreamCallback implements CachedOutputStreamCallback {
+ private final Templates xsltTemplate;
+ private final OutputStream origStream;
+
+ public XSLTCachedOutputStreamCallback(Templates xsltTemplate, OutputStream origStream) {
+ this.xsltTemplate = xsltTemplate;
+ this.origStream = origStream;
+ }
+
+ @Override
+ public void onFlush(CachedOutputStream wrapper) {
+ }
+
+ @Override
+ public void onClose(CachedOutputStream wrapper) {
+ InputStream transformedStream;
+ Exception exceptionOnClose = null;
+ try {
+ transformedStream = XSLTUtils.transform(xsltTemplate, wrapper.getInputStream());
+ IOUtils.copyAndCloseInput(transformedStream, origStream);
+ } catch (IOException e) {
+ throw new Fault("STREAM_COPY", LOG, e, e.getMessage());
+ } finally {
+ try {
+ origStream.close();
+ } catch (Exception e) {
+ exceptionOnClose = e;
+ }
+ }
+
+ if (exceptionOnClose == null) {
+ return;
+ }
+ throw new Fault(exceptionOnClose);
+ }
+ }
+
+ public static class XSLTCachedWriter extends CachedWriter {
+ private final Templates xsltTemplate;
+ private final Writer origWriter;
+
+ public XSLTCachedWriter(Templates xsltTemplate, Writer origWriter) {
+ this.xsltTemplate = xsltTemplate;
+ this.origWriter = origWriter;
+ }
+
+ @Override
+ protected void doClose() {
+ try {
+ final Reader transformedReader = XSLTUtils.transform(xsltTemplate, getReader());
+ IOUtils.copyAndCloseInput(transformedReader, origWriter, IOUtils.DEFAULT_BUFFER_SIZE);
+ } catch (IOException e) {
+ throw new Fault("READER_COPY", LOG, e, e.getMessage());
+ } finally {
+ try {
+ origWriter.close();
+ } catch (IOException e) {
+ LOG.warning("Cannot close stream after transformation: " + e.getMessage());
+ }
+ }
+ }
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/headers/Header.java b/transform/src/patch/java/org/apache/cxf/headers/Header.java
new file mode 100644
index 0000000..f933c14
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/headers/Header.java
@@ -0,0 +1,78 @@
+/**
+ * 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.headers;
+
+import javax.xml.namespace.QName;
+
+import org.apache.cxf.databinding.DataBinding;
+
+public class Header {
+ public enum Direction {
+ DIRECTION_IN,
+ DIRECTION_OUT,
+ DIRECTION_INOUT
+ }
+
+ public static final String HEADER_LIST = Header.class.getName() + ".list";
+
+
+ private DataBinding dataBinding;
+ private QName name;
+ private Object object;
+
+ private Direction direction = Header.Direction.DIRECTION_OUT;
+
+ public Header(QName q, Object o) {
+ this(q, o, null);
+ }
+
+ public Header(QName q, Object o, DataBinding b) {
+ object = o;
+ name = q;
+ dataBinding = b;
+ }
+
+ public DataBinding getDataBinding() {
+ return dataBinding;
+ }
+ public void setDataBinding(DataBinding dataBinding) {
+ this.dataBinding = dataBinding;
+ }
+ public QName getName() {
+ return name;
+ }
+ public void setName(QName name) {
+ this.name = name;
+ }
+ public Object getObject() {
+ return object;
+ }
+ public void setObject(Object object) {
+ this.object = object;
+ }
+
+ public void setDirection(Direction hdrDirection) {
+ this.direction = hdrDirection;
+ }
+
+ public Direction getDirection() {
+ return direction;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/helpers/DOMUtils.java b/transform/src/patch/java/org/apache/cxf/helpers/DOMUtils.java
new file mode 100644
index 0000000..37bde67
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/helpers/DOMUtils.java
@@ -0,0 +1,895 @@
+/**
+ * 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.helpers;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Comment;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentFragment;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.Text;
+
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.ReflectionUtil;
+import org.apache.cxf.common.util.StringUtils;
+
+/**
+ * Few simple utils to read DOM. This is originally from the Jakarta Commons Modeler.
+ */
+public final class DOMUtils {
+ private static boolean isJre9SAAJ;
+ private static final Map<ClassLoader, DocumentBuilder> DOCUMENT_BUILDERS
+ = Collections.synchronizedMap(new WeakHashMap<ClassLoader, DocumentBuilder>());
+ private static final String XMLNAMESPACE = "xmlns";
+ private static volatile Document emptyDocument;
+
+ private static final ClassValue<Method> GET_DOM_ELEMENTS_METHODS = new ClassValue<Method>() {
+ @Override
+ protected Method computeValue(Class<?> type) {
+ try {
+ return ReflectionUtil.getMethod(type, "getDomElement");
+ } catch (NoSuchMethodException e) {
+ //best effort to try, do nothing if NoSuchMethodException
+ return null;
+ }
+ }
+ };
+ private static final ClassValue<Field> GET_DOCUMENT_FRAGMENT_FIELDS = new ClassValue<Field>() {
+ @Override
+ protected Field computeValue(Class<?> type) {
+ return ReflectionUtil.getDeclaredField(type, "documentFragment");
+ }
+
+ };
+
+ static {
+ try {
+ Method[] methods = DOMUtils.class.getClassLoader().
+ loadClass("com.sun.xml.messaging.saaj.soap.SOAPDocumentImpl").getMethods();
+ for (Method method : methods) {
+ if ("register".equals(method.getName())) {
+ //this is the 1.4+ SAAJ impl
+ setJava9SAAJ(true);
+ break;
+ }
+ }
+ } catch (ClassNotFoundException cnfe) {
+ LogUtils.getL7dLogger(DOMUtils.class).finest(
+ "can't load class com.sun.xml.messaging.saaj.soap.SOAPDocumentImpl");
+
+ try {
+ Method[] methods = DOMUtils.class.getClassLoader().
+ loadClass("com.sun.xml.internal.messaging.saaj.soap.SOAPDocumentImpl").getMethods();
+ for (Method method : methods) {
+ if ("register".equals(method.getName())) {
+ //this is the SAAJ impl in JDK9
+ setJava9SAAJ(true);
+ break;
+ }
+ }
+ } catch (ClassNotFoundException cnfe1) {
+ LogUtils.getL7dLogger(DOMUtils.class).finest(
+ "can't load class com.sun.xml.internal.messaging.saaj.soap.SOAPDocumentImpl");
+ }
+ } catch (Throwable throwable) {
+ LogUtils.getL7dLogger(DOMUtils.class).finest(
+ "Other JDK vendor");
+ }
+ }
+
+ private DOMUtils() {
+ }
+
+ private static DocumentBuilder getDocumentBuilder() throws ParserConfigurationException {
+ ClassLoader loader = getContextClassLoader();
+ if (loader == null) {
+ loader = getClassLoader(DOMUtils.class);
+ }
+ if (loader == null) {
+ return createDocumentBuilder();
+ }
+ DocumentBuilder factory = DOCUMENT_BUILDERS.get(loader);
+ if (factory == null) {
+ factory = createDocumentBuilder();
+ DOCUMENT_BUILDERS.put(loader, factory);
+ }
+ return factory;
+ }
+
+ private static DocumentBuilder createDocumentBuilder() throws ParserConfigurationException {
+ DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
+ f.setNamespaceAware(true);
+ f.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
+ f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ return f.newDocumentBuilder();
+ }
+
+ private static ClassLoader getContextClassLoader() {
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+ public ClassLoader run() {
+ return Thread.currentThread().getContextClassLoader();
+ }
+ });
+ }
+ return Thread.currentThread().getContextClassLoader();
+ }
+
+ private static ClassLoader getClassLoader(final Class<?> clazz) {
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+ public ClassLoader run() {
+ return clazz.getClassLoader();
+ }
+ });
+ }
+ return clazz.getClassLoader();
+ }
+
+ /**
+ * Creates a new Document object
+ * @throws ParserConfigurationException
+ */
+ public static Document newDocument() {
+ return createDocument();
+ }
+ public static Document createDocument() {
+ try {
+ return getDocumentBuilder().newDocument();
+ } catch (ParserConfigurationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static synchronized Document createEmptyDocument() {
+ if (emptyDocument == null) {
+ emptyDocument = createDocument();
+
+ // uncomment this to see if anything is actually setting anything into the empty doc
+ /*
+ final Document doc = createDocument();
+ emptyDocument = (Document)org.apache.cxf.common.util.ProxyHelper.getProxy(
+ DOMUtils.class.getClassLoader(),
+ new Class<?>[] {Document.class},
+ new java.lang.reflect.InvocationHandler() {
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ if (method.getName().contains("create")) {
+ return method.invoke(doc, args);
+ }
+ throw new IllegalStateException("Cannot modify factory document");
+ }
+ });
+ */
+ }
+ return emptyDocument;
+ }
+ /**
+ * Returns a static Document that should always be "empty". It's useful as a factory for
+ * for creating Elements and other nodes that will be traversed later and don't need to
+ * be attached into a document
+ * @return an empty document
+ */
+ public static Document getEmptyDocument() {
+ Document doc = emptyDocument;
+ if (doc == null) {
+ doc = createEmptyDocument();
+ }
+ return doc;
+ }
+
+
+ /**
+ * This function is much like getAttribute, but returns null, not "", for a nonexistent attribute.
+ *
+ * @param e
+ * @param attributeName
+ */
+ public static String getAttributeValueEmptyNull(Element e, String attributeName) {
+ Attr node = e.getAttributeNode(attributeName);
+ if (node == null) {
+ return null;
+ }
+ return node.getValue();
+ }
+
+ /**
+ * Get the text content of a node and all it's children or null if there is no text
+ */
+ public static String getAllContent(Node n) {
+ StringBuilder b = new StringBuilder();
+ getAllContent(n, b);
+ return b.toString();
+ }
+ private static void getAllContent(Node n, StringBuilder b) {
+ Node nd = n.getFirstChild();
+ while (nd != null) {
+ if (nd instanceof Text && !(nd instanceof Comment)) {
+ b.append(((Text)nd).getData());
+ } else {
+ getAllContent(nd, b);
+ }
+ nd = nd.getNextSibling();
+ }
+ }
+ /**
+ * Get the trimmed text content of a node or null if there is no text
+ */
+ public static String getContent(Node n) {
+ String s = getRawContent(n);
+ if (s != null) {
+ s = s.trim();
+ }
+ return s;
+ }
+
+ /**
+ * Get the raw text content of a node or null if there is no text
+ */
+ public static String getRawContent(Node n) {
+ if (n == null) {
+ return null;
+ }
+ StringBuilder b = null;
+ String s = null;
+ Node n1 = n.getFirstChild();
+ while (n1 != null) {
+ if (n1.getNodeType() == Node.TEXT_NODE || n1.getNodeType() == Node.CDATA_SECTION_NODE) {
+ if (b != null) {
+ b.append(((Text)n1).getNodeValue());
+ } else if (s == null) {
+ s = ((Text)n1).getNodeValue();
+ } else {
+ b = new StringBuilder(s).append(((Text)n1).getNodeValue());
+ s = null;
+ }
+ }
+ n1 = n1.getNextSibling();
+ }
+ if (b != null) {
+ return b.toString();
+ }
+ return s;
+ }
+
+ /**
+ * Get the first element child.
+ *
+ * @param parent lookup direct childs
+ * @param name name of the element. If null return the first element.
+ */
+ public static Node getChild(Node parent, String name) {
+ if (parent == null) {
+ return null;
+ }
+
+ Node first = parent.getFirstChild();
+ if (first == null) {
+ return null;
+ }
+
+ for (Node node = first; node != null; node = node.getNextSibling()) {
+
+ if (node.getNodeType() != Node.ELEMENT_NODE) {
+ continue;
+ }
+ if (name != null && name.equals(node.getNodeName())) {
+ return node;
+ }
+ if (name == null) {
+ return node;
+ }
+ }
+ return null;
+ }
+
+
+ public static boolean hasAttribute(Element element, String value) {
+ NamedNodeMap attributes = element.getAttributes();
+ for (int i = 0; i < attributes.getLength(); i++) {
+ Node node = attributes.item(i);
+ if (value.equals(node.getNodeValue())) {
+ return true;
+ }
+ }
+ return false;
+ }
+ public static String getAttribute(Node element, String attName) {
+ NamedNodeMap attrs = element.getAttributes();
+ if (attrs == null) {
+ return null;
+ }
+ Node attN = attrs.getNamedItem(attName);
+ if (attN == null) {
+ return null;
+ }
+ return attN.getNodeValue();
+ }
+
+ public static String getAttribute(Element element, QName attName) {
+ Attr attr;
+ if (StringUtils.isEmpty(attName.getNamespaceURI())) {
+ attr = element.getAttributeNode(attName.getLocalPart());
+ } else {
+ attr = element.getAttributeNodeNS(attName.getNamespaceURI(), attName.getLocalPart());
+ }
+ return attr == null ? null : attr.getValue();
+ }
+
+ public static void setAttribute(Node node, String attName, String val) {
+ NamedNodeMap attributes = node.getAttributes();
+ Node attNode = node.getOwnerDocument().createAttributeNS(null, attName);
+ attNode.setNodeValue(val);
+ attributes.setNamedItem(attNode);
+ }
+
+ public static void removeAttribute(Node node, String attName) {
+ NamedNodeMap attributes = node.getAttributes();
+ attributes.removeNamedItem(attName);
+ }
+
+ /**
+ * Set or replace the text value
+ */
+ public static void setText(Node node, String val) {
+ Node chld = DOMUtils.getChild(node, Node.TEXT_NODE);
+ if (chld == null) {
+ Node textN = node.getOwnerDocument().createTextNode(val);
+ node.appendChild(textN);
+ return;
+ }
+ // change the value
+ chld.setNodeValue(val);
+ }
+
+ /**
+ * Find the first direct child with a given attribute.
+ *
+ * @param parent
+ * @param elemName name of the element, or null for any
+ * @param attName attribute we're looking for
+ * @param attVal attribute value or null if we just want any
+ */
+ public static Element findChildWithAtt(Node parent, String elemName, String attName, String attVal) {
+
+ Element child = (Element)getChild(parent, Node.ELEMENT_NODE);
+ if (attVal == null) {
+ while (child != null && (elemName == null || elemName.equals(child.getNodeName()))
+ && DOMUtils.getAttribute(child, attName) != null) {
+ child = (Element)getNext(child, elemName, Node.ELEMENT_NODE);
+ }
+ } else {
+ while (child != null && (elemName == null || elemName.equals(child.getNodeName()))
+ && !attVal.equals(DOMUtils.getAttribute(child, attName))) {
+ child = (Element)getNext(child, elemName, Node.ELEMENT_NODE);
+ }
+ }
+ return child;
+ }
+
+ /**
+ * Get the first child's content ( ie it's included TEXT node ).
+ */
+ public static String getChildContent(Node parent, String name) {
+ Node first = parent.getFirstChild();
+ if (first == null) {
+ return null;
+ }
+ for (Node node = first; node != null; node = node.getNextSibling()) {
+
+ if (name.equals(node.getNodeName())) {
+ return getRawContent(node);
+ }
+ }
+ return null;
+ }
+
+ public static QName getElementQName(Element el) {
+ return new QName(el.getNamespaceURI(), el.getLocalName());
+ }
+
+ /**
+ * Creates a QName object based on the qualified name
+ * and using the Node as a base to lookup the namespace
+ * for the prefix
+ * @param qualifiedName
+ * @param node
+ */
+ public static QName createQName(String qualifiedName, Node node) {
+ if (qualifiedName == null) {
+ return null;
+ }
+
+ int index = qualifiedName.indexOf(':');
+
+ if (index == -1) {
+ return new QName(qualifiedName);
+ }
+
+ String prefix = qualifiedName.substring(0, index);
+ String localName = qualifiedName.substring(index + 1);
+ String ns = node.lookupNamespaceURI(prefix);
+
+ if (ns == null) {
+ throw new RuntimeException("Invalid QName in mapping: " + qualifiedName);
+ }
+
+ return new QName(ns, localName, prefix);
+ }
+
+ public static QName convertStringToQName(String expandedQName) {
+ return convertStringToQName(expandedQName, "");
+ }
+
+ public static QName convertStringToQName(String expandedQName, String prefix) {
+ int ind1 = expandedQName.indexOf('{');
+ if (ind1 != 0) {
+ return new QName(expandedQName);
+ }
+
+ int ind2 = expandedQName.indexOf('}');
+ if (ind2 <= ind1 + 1 || ind2 >= expandedQName.length() - 1) {
+ return null;
+ }
+ String ns = expandedQName.substring(ind1 + 1, ind2);
+ String localName = expandedQName.substring(ind2 + 1);
+ return new QName(ns, localName, prefix);
+ }
+ public static Set<QName> convertStringsToQNames(List<String> expandedQNames) {
+ Set<QName> dropElements = Collections.emptySet();
+ if (expandedQNames != null) {
+ dropElements = new LinkedHashSet<>(expandedQNames.size());
+ for (String val : expandedQNames) {
+ dropElements.add(convertStringToQName(val));
+ }
+ }
+ return dropElements;
+ }
+
+
+ /**
+ * Get the first direct child with a given type
+ */
+ public static Element getFirstElement(Node parent) {
+ Node n = parent.getFirstChild();
+ while (n != null && Node.ELEMENT_NODE != n.getNodeType()) {
+ n = n.getNextSibling();
+ }
+ if (n == null) {
+ return null;
+ }
+ return (Element)n;
+ }
+
+ public static Element getNextElement(Element el) {
+ Node nd = el.getNextSibling();
+ while (nd != null) {
+ if (nd.getNodeType() == Node.ELEMENT_NODE) {
+ return (Element)nd;
+ }
+ nd = nd.getNextSibling();
+ }
+ return null;
+ }
+
+ /**
+ * Return the first element child with the specified qualified name.
+ *
+ * @param parent
+ * @param q
+ */
+ public static Element getFirstChildWithName(Element parent, QName q) {
+ String ns = q.getNamespaceURI();
+ String lp = q.getLocalPart();
+ return getFirstChildWithName(parent, ns, lp);
+ }
+
+ /**
+ * Return the first element child with the specified qualified name.
+ *
+ * @param parent
+ * @param ns
+ * @param lp
+ */
+ public static Element getFirstChildWithName(Element parent, String ns, String lp) {
+ for (Node n = parent.getFirstChild(); n != null; n = n.getNextSibling()) {
+ if (n instanceof Element) {
+ Element e = (Element)n;
+ String ens = (e.getNamespaceURI() == null) ? "" : e.getNamespaceURI();
+ if (ns.equals(ens) && lp.equals(e.getLocalName())) {
+ return e;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return child elements with specified name.
+ *
+ * @param parent
+ * @param ns
+ * @param localName
+ */
+ public static List<Element> getChildrenWithName(Element parent, String ns, String localName) {
+ List<Element> r = new ArrayList<>();
+ for (Node n = parent.getFirstChild(); n != null; n = n.getNextSibling()) {
+ if (n instanceof Element) {
+ Element e = (Element)n;
+ String eNs = (e.getNamespaceURI() == null) ? "" : e.getNamespaceURI();
+ if (ns.equals(eNs) && localName.equals(e.getLocalName())) {
+ r.add(e);
+ }
+ }
+ }
+ return r;
+ }
+
+ /**
+ * Returns all child elements with specified namespace.
+ *
+ * @param parent the element to search under
+ * @param ns the namespace to find elements in
+ * @return all child elements with specified namespace
+ */
+ public static List<Element> getChildrenWithNamespace(Element parent, String ns) {
+ List<Element> r = new ArrayList<>();
+ for (Node n = parent.getFirstChild(); n != null; n = n.getNextSibling()) {
+ if (n instanceof Element) {
+ Element e = (Element)n;
+ String eNs = (e.getNamespaceURI() == null) ? "" : e.getNamespaceURI();
+ if (ns.equals(eNs)) {
+ r.add(e);
+ }
+ }
+ }
+ return r;
+ }
+
+ /**
+ * Get the first child of the specified type.
+ *
+ * @param parent
+ * @param type
+ */
+ public static Node getChild(Node parent, int type) {
+ Node n = parent.getFirstChild();
+ while (n != null && type != n.getNodeType()) {
+ n = n.getNextSibling();
+ }
+ if (n == null) {
+ return null;
+ }
+ return n;
+ }
+
+ /**
+ * Get the next sibling with the same name and type
+ */
+ public static Node getNext(Node current) {
+ String name = current.getNodeName();
+ int type = current.getNodeType();
+ return getNext(current, name, type);
+ }
+
+ /**
+ * Return the next sibling with a given name and type
+ */
+ public static Node getNext(Node current, String name, int type) {
+ Node first = current.getNextSibling();
+ if (first == null) {
+ return null;
+ }
+
+ for (Node node = first; node != null; node = node.getNextSibling()) {
+
+ if (type >= 0 && node.getNodeType() != type) {
+ continue;
+ }
+
+ if (name == null) {
+ return node;
+ }
+ if (name.equals(node.getNodeName())) {
+ return node;
+ }
+ }
+ return null;
+ }
+
+ public static class NullResolver implements EntityResolver {
+ public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
+ return new InputSource(new StringReader(""));
+ }
+ }
+
+ public static String getPrefixRecursive(Element el, String ns) {
+ String prefix = getPrefix(el, ns);
+ if (prefix == null && el.getParentNode() instanceof Element) {
+ prefix = getPrefixRecursive((Element)el.getParentNode(), ns);
+ }
+ return prefix;
+ }
+
+ public static String getPrefix(Element el, String ns) {
+ NamedNodeMap atts = el.getAttributes();
+ for (int i = 0; i < atts.getLength(); i++) {
+ Node node = atts.item(i);
+ String name = node.getNodeName();
+ if (ns.equals(node.getNodeValue())
+ && (name != null && (XMLNAMESPACE.equals(name) || name.startsWith(XMLNAMESPACE + ":")))) {
+ return node.getLocalName();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get all prefixes defined, up to the root, for a namespace URI.
+ *
+ * @param element
+ * @param namespaceUri
+ * @param prefixes
+ */
+ public static void getPrefixesRecursive(Element element, String namespaceUri, List<String> prefixes) {
+ getPrefixes(element, namespaceUri, prefixes);
+ Node parent = element.getParentNode();
+ if (parent instanceof Element) {
+ getPrefixesRecursive((Element)parent, namespaceUri, prefixes);
+ }
+ }
+
+ /**
+ * Get all prefixes defined on this element for the specified namespace.
+ *
+ * @param element
+ * @param namespaceUri
+ * @param prefixes
+ */
+ public static void getPrefixes(Element element, String namespaceUri, List<String> prefixes) {
+ NamedNodeMap atts = element.getAttributes();
+ for (int i = 0; i < atts.getLength(); i++) {
+ Node node = atts.item(i);
+ String name = node.getNodeName();
+ if (namespaceUri.equals(node.getNodeValue())
+ && (name != null && (XMLNAMESPACE.equals(name) || name.startsWith(XMLNAMESPACE + ":")))) {
+ prefixes.add(node.getPrefix());
+ }
+ }
+ }
+
+ public static String createNamespace(Element el, String ns) {
+ String p = "ns1";
+ int i = 1;
+ while (getPrefix(el, ns) != null) {
+ p = "ns" + i;
+ i++;
+ }
+ addNamespacePrefix(el, ns, p);
+ return p;
+ }
+
+ /**
+ * Starting from a node, find the namespace declaration for a prefix. for a matching namespace
+ * declaration.
+ *
+ * @param node search up from here to search for namespace definitions
+ * @param searchPrefix the prefix we are searching for
+ * @return the namespace if found.
+ */
+ public static String getNamespace(Node node, String searchPrefix) {
+
+ Element el;
+ while (!(node instanceof Element)) {
+ node = node.getParentNode();
+ }
+ el = (Element)node;
+
+ NamedNodeMap atts = el.getAttributes();
+ for (int i = 0; i < atts.getLength(); i++) {
+ Node currentAttribute = atts.item(i);
+ String currentLocalName = currentAttribute.getLocalName();
+ String currentPrefix = currentAttribute.getPrefix();
+ if (searchPrefix.equals(currentLocalName) && XMLNAMESPACE.equals(currentPrefix)) {
+ return currentAttribute.getNodeValue();
+ } else if (StringUtils.isEmpty(searchPrefix) && XMLNAMESPACE.equals(currentLocalName)
+ && StringUtils.isEmpty(currentPrefix)) {
+ return currentAttribute.getNodeValue();
+ }
+ }
+
+ Node parent = el.getParentNode();
+ if (parent instanceof Element) {
+ return getNamespace(parent, searchPrefix);
+ }
+
+ return null;
+ }
+
+ public static List<Element> findAllElementsByTagNameNS(Element elem, String nameSpaceURI,
+ String localName) {
+ List<Element> ret = new LinkedList<>();
+ findAllElementsByTagNameNS(elem, nameSpaceURI, localName, ret);
+ return ret;
+ }
+
+ /**
+ * Try to get the DOM Node from the SAAJ Node with JAVA9 afterwards
+ * @param node The original node we need check
+ * @return The DOM node
+ */
+ public static Node getDomElement(Node node) {
+ if (node != null && isJava9SAAJ()) {
+ //java9plus hack
+ Method method = GET_DOM_ELEMENTS_METHODS.get(node.getClass());
+ if (method != null) {
+ try {
+ return (Node)ReflectionUtil.setAccessible(method).invoke(node);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return node;
+ }
+
+ /**
+ * Try to get the DOM DocumentFragment from the SAAJ DocumentFragment with JAVA9 afterwards
+ * @param fragment The original documentFragment we need to check
+ * @return The DOM DocumentFragment
+ */
+ public static DocumentFragment getDomDocumentFragment(DocumentFragment fragment) {
+ if (fragment != null && isJava9SAAJ()) {
+ //java9 plus hack
+ Field f = GET_DOCUMENT_FRAGMENT_FIELDS.get(fragment.getClass());
+ if (f != null) {
+ return ReflectionUtil.accessDeclaredField(f, fragment, DocumentFragment.class);
+ }
+ }
+ return fragment;
+ }
+
+ private static void findAllElementsByTagNameNS(Element el, String nameSpaceURI, String localName,
+ List<Element> elementList) {
+
+ if (el.getNamespaceURI() != null && localName.equals(el.getLocalName())
+ && nameSpaceURI.contains(el.getNamespaceURI())) {
+ elementList.add(el);
+ }
+ Element elem = getFirstElement(el);
+ while (elem != null) {
+ findAllElementsByTagNameNS(elem, nameSpaceURI, localName, elementList);
+ elem = getNextElement(elem);
+ }
+ }
+
+ public static List<Element> findAllElementsByTagName(Element elem, String tagName) {
+ List<Element> ret = new LinkedList<>();
+ findAllElementsByTagName(elem, tagName, ret);
+ return ret;
+ }
+
+ private static void findAllElementsByTagName(Element el, String tagName, List<Element> elementList) {
+
+ if (tagName.equals(el.getTagName())) {
+ elementList.add(el);
+ }
+ Element elem = getFirstElement(el);
+ while (elem != null) {
+ findAllElementsByTagName(elem, tagName, elementList);
+ elem = getNextElement(elem);
+ }
+ }
+ public static boolean hasElementWithName(Element el, String nameSpaceURI, String localName) {
+ if (el.getNamespaceURI() != null && localName.equals(el.getLocalName())
+ && nameSpaceURI.contains(el.getNamespaceURI())) {
+ return true;
+ }
+ Element elem = getFirstElement(el);
+ while (elem != null) {
+ if (hasElementWithName(elem, nameSpaceURI, localName)) {
+ return true;
+ }
+ elem = getNextElement(elem);
+ }
+ return false;
+ }
+ public static boolean hasElementInNS(Element el, String namespace) {
+
+ if (namespace.equals(el.getNamespaceURI())) {
+ return true;
+ }
+ Element elem = getFirstElement(el);
+ while (elem != null) {
+ if (hasElementInNS(elem, namespace)) {
+ return true;
+ }
+ elem = getNextElement(elem);
+ }
+ return false;
+ }
+ /**
+ * Set a namespace/prefix on an element if it is not set already. First off, it searches for the element
+ * for the prefix associated with the specified namespace. If the prefix isn't null, then this is
+ * returned. Otherwise, it creates a new attribute using the namespace/prefix passed as parameters.
+ *
+ * @param element
+ * @param namespace
+ * @param prefix
+ * @return the prefix associated with the set namespace
+ */
+ public static String setNamespace(Element element, String namespace, String prefix) {
+ String pre = getPrefixRecursive(element, namespace);
+ if (pre != null) {
+ return pre;
+ }
+ element.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:" + prefix, namespace);
+ return prefix;
+ }
+
+ /**
+ * Add a namespace prefix definition to an element.
+ *
+ * @param element
+ * @param namespaceUri
+ * @param prefix
+ */
+ public static void addNamespacePrefix(Element element, String namespaceUri, String prefix) {
+ element.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:" + prefix, namespaceUri);
+ }
+
+ public static boolean isJava9SAAJ() {
+ return isJre9SAAJ;
+ }
+
+ private static void setJava9SAAJ(boolean isJava9SAAJ) {
+ DOMUtils.isJre9SAAJ = isJava9SAAJ;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/helpers/IOUtils.java b/transform/src/patch/java/org/apache/cxf/helpers/IOUtils.java
new file mode 100644
index 0000000..74af28d
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/helpers/IOUtils.java
@@ -0,0 +1,428 @@
+/**
+ * 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.helpers;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PushbackInputStream;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+
+import org.apache.cxf.io.CopyingOutputStream;
+import org.apache.cxf.io.Transferable;
+
+public final class IOUtils {
+ public static final Charset UTF8_CHARSET = java.nio.charset.StandardCharsets.UTF_8;
+ public static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
+
+ private IOUtils() {
+
+ }
+
+ /**
+ * Checks if input stream is empty. If the standard InputStream means do not provide
+ * such details, the stream might be wrapped into PushbackInputStream and is going
+ * to be returned instead of original one.
+ * @param is input stream to check
+ * @return "null" if original input stream is empty, otherwise original stream or
+ * original stream wrapped into PushbackInputStream.
+ * @throws IOException
+ */
+ public static InputStream nullOrNotEmptyStream(InputStream is) throws IOException {
+ if (isEmpty(is)) {
+ return null;
+ } else if (!(is instanceof PushbackInputStream)) {
+ final byte[] bytes = new byte[1];
+
+ final PushbackInputStream pbStream = new PushbackInputStream(is);
+ boolean isEmpty = isEof(pbStream.read(bytes));
+
+ if (!isEmpty) {
+ pbStream.unread(bytes);
+ return pbStream;
+ }
+
+ return null;
+ }
+
+ return is;
+ }
+
+ public static boolean isEmpty(InputStream is) throws IOException {
+ if (is == null) {
+ return true;
+ }
+ try {
+ // if available is 0 it does not mean it is empty
+ if (is.available() > 0) {
+ return false;
+ }
+ } catch (IOException ioe) {
+ //Do nothing
+ }
+ final byte[] bytes = new byte[1];
+ if (is.markSupported()) {
+ is.mark(1);
+ try {
+ return isEof(is.read(bytes));
+ } finally {
+ is.reset();
+ }
+ }
+
+ if (!(is instanceof PushbackInputStream)) {
+ return false;
+ }
+
+ // it may be an attachment stream
+ PushbackInputStream pbStream = (PushbackInputStream)is;
+ boolean isEmpty = isEof(pbStream.read(bytes));
+ if (!isEmpty) {
+ pbStream.unread(bytes);
+ }
+
+ return isEmpty;
+ }
+ private static boolean isEof(int result) {
+ return result == -1;
+ }
+ /**
+ * Use this function instead of new String(byte[], String) to avoid surprises from
+ * non-standard default encodings.
+ * @param bytes
+ * @param charsetName
+ */
+ public static String newStringFromBytes(byte[] bytes, String charsetName) {
+ try {
+ return new String(bytes, charsetName);
+ } catch (UnsupportedEncodingException e) {
+ throw
+ new RuntimeException("Impossible failure: Charset.forName(\""
+ + charsetName + "\") returns invalid name.");
+
+ }
+ }
+
+
+ /**
+ * Use this function instead of new String(byte[]) to avoid surprises from non-standard default encodings.
+ * @param bytes
+ */
+ public static String newStringFromBytes(byte[] bytes) {
+ return newStringFromBytes(bytes, UTF8_CHARSET.name());
+ }
+
+ /**
+ * Use this function instead of new String(byte[], int, int, String)
+ * to avoid surprises from non-standard default encodings.
+ * @param bytes
+ * @param charsetName
+ * @param start
+ * @param length
+ */
+ public static String newStringFromBytes(byte[] bytes, String charsetName, int start, int length) {
+ try {
+ return new String(bytes, start, length, charsetName);
+ } catch (UnsupportedEncodingException e) {
+ throw
+ new RuntimeException("Impossible failure: Charset.forName(\""
+ + charsetName + "\") returns invalid name.");
+
+ }
+ }
+
+ /**
+ * Use this function instead of new String(byte[], int, int)
+ * to avoid surprises from non-standard default encodings.
+ * @param bytes
+ * @param start
+ * @param length
+ */
+ public static String newStringFromBytes(byte[] bytes, int start, int length) {
+ return newStringFromBytes(bytes, UTF8_CHARSET.name(), start, length);
+ }
+
+ public static int copy(final InputStream input, final OutputStream output)
+ throws IOException {
+ if (input == null) {
+ return 0;
+ }
+ if (output instanceof CopyingOutputStream) {
+ return ((CopyingOutputStream)output).copyFrom(input);
+ }
+ return copy(input, output, DEFAULT_BUFFER_SIZE);
+ }
+
+ public static int copyAndCloseInput(final InputStream input,
+ final OutputStream output) throws IOException {
+ try (InputStream in = input) {
+ return copy(in, output);
+ }
+ }
+
+ public static int copyAndCloseInput(final InputStream input,
+ final OutputStream output, int bufferSize) throws IOException {
+ try (InputStream in = input) {
+ return copy(in, output, bufferSize);
+ }
+ }
+
+ public static void copyAndCloseInput(final Reader input,
+ final Writer output) throws IOException {
+ try (Reader r = input) {
+ copy(r, output, DEFAULT_BUFFER_SIZE);
+ }
+ }
+
+ public static void copyAndCloseInput(final Reader input,
+ final Writer output, int bufferSize) throws IOException {
+ try (Reader r = input) {
+ copy(r, output, bufferSize);
+ }
+ }
+
+ public static int copy(final InputStream input, final OutputStream output,
+ int bufferSize) throws IOException {
+ int avail = input.available();
+ if (avail > 262144) {
+ avail = 262144;
+ }
+ if (avail > bufferSize) {
+ bufferSize = avail;
+ }
+ final byte[] buffer = new byte[bufferSize];
+ int n = input.read(buffer);
+ int total = 0;
+ while (-1 != n) {
+ if (n == 0) {
+ throw new IOException("0 bytes read in violation of InputStream.read(byte[])");
+ }
+ output.write(buffer, 0, n);
+ total += n;
+ n = input.read(buffer);
+ }
+ return total;
+ }
+
+ /**
+ * Copy at least the specified number of bytes from the input to the output
+ * or until the inputstream is finished.
+ * @param input
+ * @param output
+ * @param atLeast
+ * @throws IOException
+ */
+ public static void copyAtLeast(final InputStream input,
+ final OutputStream output,
+ int atLeast) throws IOException {
+ final byte[] buffer = new byte[4096];
+ int n = atLeast > buffer.length ? buffer.length : atLeast;
+ n = input.read(buffer, 0, n);
+ while (-1 != n) {
+ if (n == 0) {
+ throw new IOException("0 bytes read in violation of InputStream.read(byte[])");
+ }
+ output.write(buffer, 0, n);
+ atLeast -= n;
+ if (atLeast <= 0) {
+ return;
+ }
+ n = atLeast > buffer.length ? buffer.length : atLeast;
+ n = input.read(buffer, 0, n);
+ }
+ }
+
+ public static void copyAtLeast(final Reader input,
+ final Writer output,
+ int atLeast) throws IOException {
+ final char[] buffer = new char[4096];
+ int n = atLeast > buffer.length ? buffer.length : atLeast;
+ n = input.read(buffer, 0, n);
+ while (-1 != n) {
+ if (n == 0) {
+ throw new IOException("0 bytes read in violation of Reader.read(char[])");
+ }
+ output.write(buffer, 0, n);
+ atLeast -= n;
+ if (atLeast <= 0) {
+ return;
+ }
+ n = atLeast > buffer.length ? buffer.length : atLeast;
+ n = input.read(buffer, 0, n);
+ }
+ }
+
+
+ public static void copy(final Reader input, final Writer output,
+ final int bufferSize) throws IOException {
+ final char[] buffer = new char[bufferSize];
+ int n = input.read(buffer);
+ while (-1 != n) {
+ output.write(buffer, 0, n);
+ n = input.read(buffer);
+ }
+ }
+
+ public static void transferTo(InputStream inputStream, File destinationFile) throws IOException {
+ if (Transferable.class.isAssignableFrom(inputStream.getClass())) {
+ ((Transferable)inputStream).transferTo(destinationFile);
+ } else {
+ try (OutputStream out = Files.newOutputStream(destinationFile.toPath())) {
+ copyAndCloseInput(inputStream, out);
+ }
+ }
+ }
+
+
+ public static String toString(final InputStream input) throws IOException {
+ return toString(input, DEFAULT_BUFFER_SIZE);
+ }
+ public static String toString(final InputStream input, String charset) throws IOException {
+ return toString(input, DEFAULT_BUFFER_SIZE, charset);
+ }
+ public static String toString(final InputStream input, int bufferSize)
+ throws IOException {
+ return toString(input, bufferSize, null);
+ }
+ public static String toString(final InputStream input, int bufferSize, String charset)
+ throws IOException {
+
+
+ int avail = input.available();
+ if (avail > bufferSize) {
+ bufferSize = avail;
+ }
+ Reader reader = charset == null ? new InputStreamReader(input, UTF8_CHARSET)
+ : new InputStreamReader(input, charset);
+ return toString(reader, bufferSize);
+ }
+
+ public static String toString(final Reader input) throws IOException {
+ return toString(input, DEFAULT_BUFFER_SIZE);
+ }
+ public static String toString(final Reader input, int bufSize) throws IOException {
+
+ StringBuilder buf = new StringBuilder();
+ final char[] buffer = new char[bufSize];
+ try (Reader r = input) {
+ int n = r.read(buffer);
+ while (-1 != n) {
+ if (n == 0) {
+ throw new IOException("0 bytes read in violation of InputStream.read(byte[])");
+ }
+ buf.append(buffer, 0, n);
+ n = r.read(buffer);
+ }
+ return buf.toString();
+ }
+ }
+
+ public static String readStringFromStream(InputStream in)
+ throws IOException {
+ return toString(in);
+ }
+
+ /**
+ * Load the InputStream into memory and return a ByteArrayInputStream that
+ * represents it. Closes the in stream.
+ *
+ * @param in
+ * @throws IOException
+ */
+ public static ByteArrayInputStream loadIntoBAIS(InputStream in)
+ throws IOException {
+ int i = in.available();
+ if (i < DEFAULT_BUFFER_SIZE) {
+ i = DEFAULT_BUFFER_SIZE;
+ }
+ LoadingByteArrayOutputStream bout = new LoadingByteArrayOutputStream(i);
+ copy(in, bout);
+ in.close();
+ return bout.createInputStream();
+ }
+
+ public static void consume(InputStream in) throws IOException {
+ int i = in.available();
+ if (i == 0) {
+ //if i is 0, then we MAY have already hit the end of the stream
+ //so try a read and return rather than allocate a buffer and such
+ int i2 = in.read();
+ if (i2 == -1) {
+ return;
+ }
+ //reading the byte may have caused a buffer to fill
+ i = in.available();
+ }
+ if (i < DEFAULT_BUFFER_SIZE) {
+ i = DEFAULT_BUFFER_SIZE;
+ }
+ if (i > 65536) {
+ i = 65536;
+ }
+ byte[] bytes = new byte[i];
+ while (in.read(bytes) != -1) {
+ //nothing - just discarding
+ }
+ }
+
+ /**
+ * Consumes at least the given number of bytes from the input stream
+ * @param input
+ * @param atLeast
+ * @throws IOException
+ */
+ public static void consume(final InputStream input,
+ int atLeast) throws IOException {
+ final byte[] buffer = new byte[4096];
+ int n = atLeast > buffer.length ? buffer.length : atLeast;
+ n = input.read(buffer, 0, n);
+ while (-1 != n) {
+ if (n == 0) {
+ throw new IOException("0 bytes read in violation of InputStream.read(byte[])");
+ }
+ atLeast -= n;
+ if (atLeast <= 0) {
+ return;
+ }
+ n = atLeast > buffer.length ? buffer.length : atLeast;
+ n = input.read(buffer, 0, n);
+ }
+ }
+
+ public static byte[] readBytesFromStream(InputStream in) throws IOException {
+ int i = in.available();
+ if (i < DEFAULT_BUFFER_SIZE) {
+ i = DEFAULT_BUFFER_SIZE;
+ }
+ try (InputStream input = in; ByteArrayOutputStream bos = new ByteArrayOutputStream(i)) {
+ copy(input, bos);
+ return bos.toByteArray();
+ }
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/helpers/ServiceUtils.java b/transform/src/patch/java/org/apache/cxf/helpers/ServiceUtils.java
new file mode 100644
index 0000000..a39ee94
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/helpers/ServiceUtils.java
@@ -0,0 +1,214 @@
+/**
+ * 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.helpers;
+
+import java.util.StringTokenizer;
+
+import org.apache.cxf.annotations.SchemaValidation.SchemaValidationType;
+import org.apache.cxf.endpoint.Endpoint;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageUtils;
+import org.apache.cxf.service.model.AbstractPropertiesHolder;
+import org.apache.cxf.service.model.BindingOperationInfo;
+import org.apache.cxf.service.model.EndpointInfo;
+import org.apache.cxf.service.model.OperationInfo;
+
+public final class ServiceUtils {
+
+ private ServiceUtils() {
+ }
+
+ /**
+ * A short cut method to be able to test for if Schema Validation should be enabled
+ * for IN or OUT without having to check BOTH and IN or OUT.
+ *
+ * @param message
+ * @param type
+ */
+ public static boolean isSchemaValidationEnabled(SchemaValidationType type, Message message) {
+ SchemaValidationType validationType = getSchemaValidationType(message);
+
+ boolean isRequestor = MessageUtils.isRequestor(message);
+ if (SchemaValidationType.REQUEST.equals(validationType)) {
+ if (isRequestor) {
+ validationType = SchemaValidationType.OUT;
+ } else {
+ validationType = SchemaValidationType.IN;
+ }
+ } else if (SchemaValidationType.RESPONSE.equals(validationType)) {
+ if (isRequestor) {
+ validationType = SchemaValidationType.IN;
+ } else {
+ validationType = SchemaValidationType.OUT;
+ }
+ }
+
+ return validationType.equals(type)
+ || ((SchemaValidationType.IN.equals(type) || SchemaValidationType.OUT.equals(type))
+ && SchemaValidationType.BOTH.equals(validationType));
+ }
+ /**
+ * A convenience method to check for schema validation config in the message context, and then in the service model.
+ * Does not modify the Message context (other than what is done in the getContextualProperty itself)
+ *
+ * @param message
+ */
+ public static SchemaValidationType getSchemaValidationType(Message message) {
+ SchemaValidationType validationType = getOverrideSchemaValidationType(message);
+ if (validationType == null) {
+ validationType = getSchemaValidationTypeFromModel(message);
+ }
+ if (validationType == null) {
+ Object obj = message.getContextualProperty(Message.SCHEMA_VALIDATION_ENABLED);
+ if (obj != null) {
+ validationType = getSchemaValidationType(obj);
+ }
+ }
+ if (validationType == null) {
+ validationType = SchemaValidationType.NONE;
+ }
+
+ return validationType;
+ }
+
+ private static SchemaValidationType getOverrideSchemaValidationType(Message message) {
+ Object obj = message.get(Message.SCHEMA_VALIDATION_ENABLED);
+ if (obj == null && message.getExchange() != null) {
+ obj = message.getExchange().get(Message.SCHEMA_VALIDATION_ENABLED);
+ }
+ if (obj != null) {
+ // this method will transform the legacy enabled as well
+ return getSchemaValidationType(obj);
+ }
+ return null;
+ }
+
+ private static SchemaValidationType getSchemaValidationTypeFromModel(Message message) {
+ Exchange exchange = message.getExchange();
+ SchemaValidationType validationType = null;
+
+ if (exchange != null) {
+
+ BindingOperationInfo boi = exchange.getBindingOperationInfo();
+ if (boi != null) {
+ OperationInfo opInfo = boi.getOperationInfo();
+ if (opInfo != null) {
+ validationType = getSchemaValidationTypeFromModel(opInfo);
+ }
+ }
+
+ if (validationType == null) {
+ Endpoint endpoint = exchange.getEndpoint();
+ if (endpoint != null) {
+ EndpointInfo ep = endpoint.getEndpointInfo();
+ if (ep != null) {
+ validationType = getSchemaValidationTypeFromModel(ep);
+ }
+ }
+ }
+ }
+
+ return validationType;
+ }
+
+ private static SchemaValidationType getSchemaValidationTypeFromModel(
+ AbstractPropertiesHolder properties) {
+ Object obj = properties.getProperty(Message.SCHEMA_VALIDATION_TYPE);
+ if (obj != null) {
+ return getSchemaValidationType(obj);
+ }
+ return null;
+ }
+
+ public static SchemaValidationType getSchemaValidationType(Object obj) {
+ if (obj instanceof SchemaValidationType) {
+ return (SchemaValidationType)obj;
+ } else if (obj != null) {
+ String value = obj.toString().toUpperCase(); // handle boolean values as well
+ if ("TRUE".equals(value)) {
+ return SchemaValidationType.BOTH;
+ } else if ("FALSE".equals(value)) {
+ return SchemaValidationType.NONE;
+ } else if (value.length() > 0) {
+ return SchemaValidationType.valueOf(value);
+ }
+ }
+
+ // fall through default value
+ return SchemaValidationType.NONE;
+ }
+
+ /**
+ * Generates a suitable service name from a given class. The returned name
+ * is the simple name of the class, i.e. without the package name.
+ *
+ * @param clazz the class.
+ * @return the name.
+ */
+ public static String makeServiceNameFromClassName(Class<?> clazz) {
+ String name = clazz.getName();
+ int last = name.lastIndexOf('.');
+ if (last != -1) {
+ name = name.substring(last + 1);
+ }
+
+ int inner = name.lastIndexOf('$');
+ if (inner != -1) {
+ name = name.substring(inner + 1);
+ }
+
+ return name;
+ }
+
+ /**
+ * Generates the name of a XML namespace from a given class name and
+ * protocol. The returned namespace will take the form
+ * <code>protocol://domain</code>, where <code>protocol</code> is the
+ * given protocol, and <code>domain</code> the inversed package name of
+ * the given class name. <p/> For instance, if the given class name is
+ * <code>org.codehaus.xfire.services.Echo</code>, and the protocol is
+ * <code>http</code>, the resulting namespace would be
+ * <code>http://services.xfire.codehaus.org</code>.
+ *
+ * @param className the class name
+ * @param protocol the protocol (eg. <code>http</code>)
+ * @return the namespace
+ */
+ public static String makeNamespaceFromClassName(String className, String protocol) {
+ int index = className.lastIndexOf('.');
+
+ if (index == -1) {
+ return protocol + "://" + "DefaultNamespace";
+ }
+
+ String packageName = className.substring(0, index);
+
+ StringTokenizer st = new StringTokenizer(packageName, ".");
+ String[] words = new String[st.countTokens()];
+
+ for (int i = words.length - 1; i >= 0; --i) {
+ words[i] = st.nextToken();
+ }
+
+ return protocol + "://" + String.join(".", words) + "/";
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/interceptor/AbstractFaultChainInitiatorObserver.java b/transform/src/patch/java/org/apache/cxf/interceptor/AbstractFaultChainInitiatorObserver.java
new file mode 100644
index 0000000..70d5ae0
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/interceptor/AbstractFaultChainInitiatorObserver.java
@@ -0,0 +1,142 @@
+/**
+ * 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.interceptor;
+
+import java.util.SortedSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.classloader.ClassLoaderUtils.ClassLoaderHolder;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.FaultMode;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageImpl;
+import org.apache.cxf.phase.Phase;
+import org.apache.cxf.phase.PhaseInterceptorChain;
+import org.apache.cxf.service.model.BindingFaultInfo;
+import org.apache.cxf.transport.MessageObserver;
+
+public abstract class AbstractFaultChainInitiatorObserver implements MessageObserver {
+
+ private static final Logger LOG = LogUtils.getL7dLogger(AbstractFaultChainInitiatorObserver.class);
+
+ private Bus bus;
+ private ClassLoader loader;
+
+ public AbstractFaultChainInitiatorObserver(Bus bus) {
+ this.bus = bus;
+ if (bus != null) {
+ loader = bus.getExtension(ClassLoader.class);
+ }
+ }
+
+ public void onMessage(Message message) {
+
+ assert null != message;
+
+ Bus origBus = BusFactory.getAndSetThreadDefaultBus(bus);
+ ClassLoaderHolder origLoader = null;
+ try {
+ if (loader != null) {
+ origLoader = ClassLoaderUtils.setThreadContextClassloader(loader);
+ }
+
+ Exchange exchange = message.getExchange();
+
+ Message faultMessage;
+
+ // now that we have switched over to the fault chain,
+ // prevent any further operations on the in/out message
+
+ if (isOutboundObserver()) {
+ Exception ex = message.getContent(Exception.class);
+ if (!(ex instanceof Fault)) {
+ ex = new Fault(ex);
+ }
+ FaultMode mode = message.get(FaultMode.class);
+
+ faultMessage = exchange.getOutMessage();
+ if (null == faultMessage) {
+ faultMessage = new MessageImpl();
+ faultMessage.setExchange(exchange);
+ faultMessage = exchange.getEndpoint().getBinding().createMessage(faultMessage);
+ }
+ faultMessage.setContent(Exception.class, ex);
+ if (null != mode) {
+ faultMessage.put(FaultMode.class, mode);
+ }
+ //CXF-3981
+ if (message.get("javax.xml.ws.addressing.context.inbound") != null) {
+ faultMessage.put("javax.xml.ws.addressing.context.inbound",
+ message.get("javax.xml.ws.addressing.context.inbound"));
+ }
+ exchange.setOutMessage(null);
+ exchange.setOutFaultMessage(faultMessage);
+ if (message.get(BindingFaultInfo.class) != null) {
+ faultMessage.put(BindingFaultInfo.class, message.get(BindingFaultInfo.class));
+ }
+ } else {
+ faultMessage = message;
+ exchange.setInMessage(null);
+ exchange.setInFaultMessage(faultMessage);
+ }
+
+
+ // setup chain
+ PhaseInterceptorChain chain = new PhaseInterceptorChain(getPhases());
+ initializeInterceptors(faultMessage.getExchange(), chain);
+
+ faultMessage.setInterceptorChain(chain);
+ try {
+ chain.doIntercept(faultMessage);
+ } catch (RuntimeException exc) {
+ LOG.log(Level.SEVERE, "ERROR_DURING_ERROR_PROCESSING", exc);
+ throw exc;
+ } catch (Exception exc) {
+ LOG.log(Level.SEVERE, "ERROR_DURING_ERROR_PROCESSING", exc);
+ throw new RuntimeException(exc);
+ }
+ } finally {
+ if (origBus != bus) {
+ BusFactory.setThreadDefaultBus(origBus);
+ }
+ if (origLoader != null) {
+ origLoader.reset();
+ }
+ }
+ }
+
+ protected abstract boolean isOutboundObserver();
+
+ protected abstract SortedSet<Phase> getPhases();
+
+ protected void initializeInterceptors(Exchange ex, PhaseInterceptorChain chain) {
+
+ }
+
+ public Bus getBus() {
+ return bus;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/interceptor/AbstractLoggingInterceptor.java b/transform/src/patch/java/org/apache/cxf/interceptor/AbstractLoggingInterceptor.java
new file mode 100644
index 0000000..5178453
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/interceptor/AbstractLoggingInterceptor.java
@@ -0,0 +1,298 @@
+/**
+ * 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.interceptor;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.PropertyUtils;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.endpoint.Endpoint;
+import org.apache.cxf.io.CachedOutputStream;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.AbstractPhaseInterceptor;
+import org.apache.cxf.service.model.EndpointInfo;
+import org.apache.cxf.service.model.InterfaceInfo;
+import org.apache.cxf.staxutils.PrettyPrintXMLStreamWriter;
+import org.apache.cxf.staxutils.StaxUtils;
+
+/**
+ * A simple logging handler which outputs the bytes of the message to the
+ * Logger.
+ * @deprecated
+ */
+@Deprecated
+public abstract class AbstractLoggingInterceptor extends AbstractPhaseInterceptor<Message> {
+ public static final int DEFAULT_LIMIT = 48 * 1024;
+ protected static final String BINARY_CONTENT_MESSAGE = "--- Binary Content ---";
+ protected static final String MULTIPART_CONTENT_MESSAGE = "--- Multipart Content ---";
+ private static final String MULTIPART_CONTENT_MEDIA_TYPE = "multipart";
+ private static final String LIVE_LOGGING_PROP = "org.apache.cxf.logging.enable";
+ private static final List<String> BINARY_CONTENT_MEDIA_TYPES = Arrays.asList(
+ "application/octet-stream", "application/pdf", "image/png", "image/jpeg", "image/gif");
+
+ protected int limit = DEFAULT_LIMIT;
+ protected long threshold = -1;
+ protected PrintWriter writer;
+ protected boolean prettyLogging;
+ private boolean showBinaryContent;
+ private boolean showMultipartContent = true;
+ private List<String> binaryContentMediaTypes = BINARY_CONTENT_MEDIA_TYPES;
+
+ public AbstractLoggingInterceptor(String phase) {
+ super(phase);
+ }
+ public AbstractLoggingInterceptor(String id, String phase) {
+ super(id, phase);
+ }
+
+ protected static boolean isLoggingDisabledNow(Message message) {
+ Object liveLoggingProp = message.getContextualProperty(LIVE_LOGGING_PROP);
+ return liveLoggingProp != null && PropertyUtils.isFalse(liveLoggingProp);
+ }
+
+ protected abstract Logger getLogger();
+
+ Logger getMessageLogger(Message message) {
+ if (isLoggingDisabledNow(message)) {
+ return null;
+ }
+ Endpoint ep = message.getExchange().getEndpoint();
+ if (ep == null || ep.getEndpointInfo() == null) {
+ return getLogger();
+ }
+ EndpointInfo endpoint = ep.getEndpointInfo();
+ if (endpoint.getService() == null) {
+ return getLogger();
+ }
+ Logger logger = endpoint.getProperty("MessageLogger", Logger.class);
+ if (logger == null) {
+ String serviceName = endpoint.getService().getName().getLocalPart();
+ InterfaceInfo iface = endpoint.getService().getInterface();
+ String portName = endpoint.getName().getLocalPart();
+ String portTypeName = iface.getName().getLocalPart();
+ String logName = "org.apache.cxf.services." + serviceName + "."
+ + portName + "." + portTypeName;
+ logger = LogUtils.getL7dLogger(this.getClass(), null, logName);
+ endpoint.setProperty("MessageLogger", logger);
+ }
+ return logger;
+ }
+
+ public void setOutputLocation(String s) {
+ if (s == null || "<logger>".equals(s)) {
+ writer = null;
+ } else if ("<stdout>".equals(s)) {
+ writer = new PrintWriter(System.out, true);
+ } else if ("<stderr>".equals(s)) {
+ writer = new PrintWriter(System.err, true);
+ } else {
+ try {
+ URI uri = new URI(s);
+ File file = new File(uri);
+ writer = new PrintWriter(new FileWriter(file, true), true);
+ } catch (Exception ex) {
+ getLogger().log(Level.WARNING, "Error configuring log location " + s, ex);
+ }
+ }
+ }
+
+ public void setPrintWriter(PrintWriter w) {
+ writer = w;
+ }
+
+ public PrintWriter getPrintWriter() {
+ return writer;
+ }
+
+ public void setLimit(int lim) {
+ limit = lim;
+ }
+
+ public int getLimit() {
+ return limit;
+ }
+
+ public void setPrettyLogging(boolean flag) {
+ prettyLogging = flag;
+ }
+
+ public boolean isPrettyLogging() {
+ return prettyLogging;
+ }
+
+ public void setInMemThreshold(long t) {
+ threshold = t;
+ }
+
+ public long getInMemThreshold() {
+ return threshold;
+ }
+
+ protected void writePayload(StringBuilder builder, CachedOutputStream cos,
+ String encoding, String contentType, boolean truncated)
+ throws Exception {
+ // Just transform the XML message when the cos has content
+ if (!truncated && isPrettyLogging() && contentType != null && contentType.contains("xml")
+ && !contentType.toLowerCase().contains("multipart/related") && cos.size() > 0) {
+
+ StringWriter swriter = new StringWriter();
+ XMLStreamWriter xwriter = StaxUtils.createXMLStreamWriter(swriter);
+ xwriter = new PrettyPrintXMLStreamWriter(xwriter, 2);
+ try (InputStream in = cos.getInputStream()) {
+ InputStreamReader inputStreamReader = StringUtils.isEmpty(encoding)
+ ? new InputStreamReader(in) : new InputStreamReader(in, encoding);
+ StaxUtils.copy(new StreamSource(inputStreamReader), xwriter);
+ } catch (XMLStreamException xse) {
+ //ignore
+ } finally {
+ try {
+ xwriter.flush();
+ xwriter.close();
+ } catch (XMLStreamException xse2) {
+ //ignore
+ }
+ }
+
+ String result = swriter.toString();
+ if (result.length() < limit || limit == -1) {
+ builder.append(result);
+ } else {
+ builder.append(result.substring(0, limit));
+ }
+
+ } else {
+ if (StringUtils.isEmpty(encoding)) {
+ cos.writeCacheTo(builder, limit);
+ } else {
+ cos.writeCacheTo(builder, encoding, limit);
+ }
+ }
+ }
+ protected void writePayload(StringBuilder builder,
+ StringWriter stringWriter,
+ String contentType)
+ throws Exception {
+ if (isPrettyLogging()
+ && contentType != null
+ && contentType.contains("xml")
+ && stringWriter.getBuffer().length() > 0) {
+ try {
+ writePrettyPayload(builder, stringWriter, contentType);
+ return;
+ } catch (Exception ex) {
+ // log it as is
+ }
+ }
+ StringBuffer buffer = stringWriter.getBuffer();
+ if (buffer.length() > limit) {
+ builder.append(buffer.subSequence(0, limit));
+ } else {
+ builder.append(buffer);
+ }
+ }
+ protected void writePrettyPayload(StringBuilder builder,
+ StringWriter stringWriter,
+ String contentType)
+ throws Exception {
+ // Just transform the XML message when the cos has content
+
+ StringWriter swriter = new StringWriter();
+ XMLStreamWriter xwriter = StaxUtils.createXMLStreamWriter(swriter);
+ xwriter = new PrettyPrintXMLStreamWriter(xwriter, 2);
+ StaxUtils.copy(new StreamSource(new StringReader(stringWriter.getBuffer().toString())), xwriter);
+ xwriter.close();
+
+ String result = swriter.toString();
+ if (result.length() < limit || limit == -1) {
+ builder.append(swriter.toString());
+ } else {
+ builder.append(swriter.toString().substring(0, limit));
+ }
+ }
+
+
+ /**
+ * Transform the string before display. The implementation in this class
+ * does nothing. Override this method if you wish to change the contents of the
+ * logged message before it is delivered to the output.
+ * For example, you can use this to mask out sensitive information.
+ * @param originalLogString the raw log message.
+ * @return transformed data
+ */
+ protected String transform(String originalLogString) {
+ return originalLogString;
+ }
+
+ protected void log(Logger logger, String message) {
+ message = transform(message);
+ if (writer != null) {
+ writer.println(message);
+ // Flushing the writer to make sure the message is written
+ writer.flush();
+ } else if (logger.isLoggable(Level.INFO)) {
+ LogRecord lr = new LogRecord(Level.INFO, message);
+ lr.setSourceClassName(logger.getName());
+ lr.setSourceMethodName(null);
+ lr.setLoggerName(logger.getName());
+ logger.log(lr);
+ }
+ }
+ public void setShowBinaryContent(boolean showBinaryContent) {
+ this.showBinaryContent = showBinaryContent;
+ }
+ public boolean isShowBinaryContent() {
+ return showBinaryContent;
+ }
+ protected boolean isBinaryContent(String contentType) {
+ return contentType != null && binaryContentMediaTypes.contains(contentType);
+ }
+ public boolean isShowMultipartContent() {
+ return showMultipartContent;
+ }
+ public void setShowMultipartContent(boolean showMultipartContent) {
+ this.showMultipartContent = showMultipartContent;
+ }
+ protected boolean isMultipartContent(String contentType) {
+ return contentType != null && contentType.startsWith(MULTIPART_CONTENT_MEDIA_TYPE);
+ }
+ public List<String> getBinaryContentMediaTypes() {
+ return binaryContentMediaTypes;
+ }
+ public void setBinaryContentMediaTypes(List<String> binaryContentMediaTypes) {
+ this.binaryContentMediaTypes = binaryContentMediaTypes;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/interceptor/AnnotationInterceptors.java b/transform/src/patch/java/org/apache/cxf/interceptor/AnnotationInterceptors.java
new file mode 100644
index 0000000..8cc757e
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/interceptor/AnnotationInterceptors.java
@@ -0,0 +1,151 @@
+/**
+ * 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.interceptor;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ResourceBundle;
+
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.i18n.BundleUtils;
+import org.apache.cxf.feature.Feature;
+import org.apache.cxf.feature.Features;
+import org.apache.cxf.message.Message;
+
+public class AnnotationInterceptors {
+
+ private static final ResourceBundle BUNDLE = BundleUtils.getBundle(AnnotationInterceptors.class);
+
+ private Class<?>[] clazzes;
+
+ public AnnotationInterceptors(Class<?> ... clz) {
+ clazzes = clz;
+ }
+
+ private <T> List<T> getAnnotationObject(Class<? extends Annotation> annotationClazz, Class<T> type) {
+
+ for (Class<?> cls : clazzes) {
+ Annotation annotation = cls.getAnnotation(annotationClazz);
+ if (annotation != null) {
+ return initializeAnnotationObjects(annotation, type);
+ }
+ }
+ return null;
+ }
+
+ private <T> List<T> initializeAnnotationObjects(Annotation annotation,
+ Class<T> type) {
+ List<T> list = new ArrayList<>();
+ for (String cn : getAnnotationObjectNames(annotation)) {
+ list.add(initializeAnnotationObject(cn, type));
+ }
+ for (Class<? extends T> cn : getAnnotationObjectClasses(annotation, type)) {
+ list.add(initializeAnnotationObject(cn));
+ }
+ return list;
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T> Class<? extends T>[] getAnnotationObjectClasses(Annotation ann, Class<T> type) { //NOPMD
+ if (ann instanceof InFaultInterceptors) {
+ return (Class<? extends T>[])((InFaultInterceptors)ann).classes();
+ } else if (ann instanceof InInterceptors) {
+ return (Class<? extends T>[])((InInterceptors)ann).classes();
+ } else if (ann instanceof OutFaultInterceptors) {
+ return (Class<? extends T>[])((OutFaultInterceptors)ann).classes();
+ } else if (ann instanceof OutInterceptors) {
+ return (Class<? extends T>[])((OutInterceptors)ann).classes();
+ } else if (ann instanceof Features) {
+ return (Class<? extends T>[])((Features)ann).classes();
+ }
+ throw new UnsupportedOperationException("Doesn't support the annotation: " + ann);
+ }
+
+ private String[] getAnnotationObjectNames(Annotation ann) {
+ if (ann instanceof InFaultInterceptors) {
+ return ((InFaultInterceptors)ann).interceptors();
+ } else if (ann instanceof InInterceptors) {
+ return ((InInterceptors)ann).interceptors();
+ } else if (ann instanceof OutFaultInterceptors) {
+ return ((OutFaultInterceptors)ann).interceptors();
+ } else if (ann instanceof OutInterceptors) {
+ return ((OutInterceptors)ann).interceptors();
+ } else if (ann instanceof Features) {
+ return ((Features)ann).features();
+ }
+
+ throw new UnsupportedOperationException("Doesn't support the annotation: " + ann);
+ }
+
+ private <T> T initializeAnnotationObject(String annObjectName, Class<T> type) {
+ try {
+ final Object object = ClassLoaderUtils.loadClass(annObjectName, this.getClass()).newInstance();
+ return type.cast(object);
+ } catch (Throwable e) {
+ throw new Fault(new org.apache.cxf.common.i18n.Message(
+ "COULD_NOT_CREATE_ANNOTATION_OBJECT",
+ BUNDLE, annObjectName), e);
+ }
+ }
+ private <T> T initializeAnnotationObject(Class<T> type) {
+ try {
+ return type.cast(type.newInstance());
+ } catch (Throwable e) {
+ throw new Fault(new org.apache.cxf.common.i18n.Message(
+ "COULD_NOT_CREATE_ANNOTATION_OBJECT",
+ BUNDLE, type.getName()), e);
+ }
+ }
+
+ private List<Interceptor<? extends Message>> getAnnotationInterceptorList(Class<? extends Annotation> t) {
+ @SuppressWarnings("rawtypes")
+ List<Interceptor> i = getAnnotationObject(t, Interceptor.class);
+ if (i == null) {
+ return null;
+ }
+ List<Interceptor<? extends Message>> m = new ArrayList<>();
+ for (Interceptor<?> i2 : i) {
+ m.add(i2);
+ }
+ return m;
+ }
+
+ public List<Interceptor<? extends Message>> getInFaultInterceptors() {
+ return getAnnotationInterceptorList(InFaultInterceptors.class);
+ }
+
+ public List<Interceptor<? extends Message>> getInInterceptors() {
+ return getAnnotationInterceptorList(InInterceptors.class);
+ }
+
+ public List<Interceptor<? extends Message>> getOutFaultInterceptors() {
+ return getAnnotationInterceptorList(OutFaultInterceptors.class);
+ }
+
+ public List<Interceptor<? extends Message>> getOutInterceptors() {
+ return getAnnotationInterceptorList(OutInterceptors.class);
+ }
+
+ public List<Feature> getFeatures() {
+ return getAnnotationObject(Features.class, Feature.class);
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/interceptor/AttachmentInInterceptor.java b/transform/src/patch/java/org/apache/cxf/interceptor/AttachmentInInterceptor.java
new file mode 100644
index 0000000..0a56be1
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/interceptor/AttachmentInInterceptor.java
@@ -0,0 +1,72 @@
+/**
+ * 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.interceptor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Logger;
+
+import org.apache.cxf.attachment.AttachmentDeserializer;
+import org.apache.cxf.attachment.AttachmentUtil;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.AbstractPhaseInterceptor;
+import org.apache.cxf.phase.Phase;
+
+public class AttachmentInInterceptor extends AbstractPhaseInterceptor<Message> {
+
+ private static final Logger LOG = LogUtils.getL7dLogger(AttachmentInInterceptor.class);
+
+ private static final List<String> TYPES = Collections.singletonList("multipart/related");
+
+ public AttachmentInInterceptor() {
+ super(Phase.RECEIVE);
+ }
+
+ public void handleMessage(Message message) {
+ if (isGET(message)) {
+ LOG.fine("AttachmentInInterceptor skipped in HTTP GET method");
+ return;
+ }
+ if (message.getContent(InputStream.class) == null) {
+ return;
+ }
+
+ String contentType = (String) message.get(Message.CONTENT_TYPE);
+ if (AttachmentUtil.isTypeSupported(contentType, getSupportedTypes())) {
+ AttachmentDeserializer ad = new AttachmentDeserializer(message, getSupportedTypes());
+ try {
+ ad.initializeAttachments();
+ } catch (IOException e) {
+ throw new Fault(e);
+ }
+ }
+ }
+
+ @Override
+ public void handleFault(Message messageParam) {
+ }
+
+ protected List<String> getSupportedTypes() {
+ return TYPES;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/interceptor/ClientOutFaultObserver.java b/transform/src/patch/java/org/apache/cxf/interceptor/ClientOutFaultObserver.java
new file mode 100644
index 0000000..341b3b0
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/interceptor/ClientOutFaultObserver.java
@@ -0,0 +1,68 @@
+/**
+ * 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.interceptor;
+
+import java.util.Map;
+import java.util.SortedSet;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.endpoint.Client;
+import org.apache.cxf.endpoint.ClientCallback;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.Phase;
+import org.apache.cxf.phase.PhaseManager;
+
+public class ClientOutFaultObserver extends AbstractFaultChainInitiatorObserver {
+
+ public ClientOutFaultObserver(Bus bus) {
+ super(bus);
+ }
+
+ @Override
+ protected SortedSet<Phase> getPhases() {
+ return getBus().getExtension(PhaseManager.class).getOutPhases();
+ }
+
+ /**
+ * override the super class method
+ */
+ @Override
+ public void onMessage(Message m) {
+ if (m.get(Message.INBOUND_MESSAGE).equals(Boolean.TRUE)) {
+ //it's outbound fault observer so only take care of outbound fault
+ return;
+ }
+ Exception ex = m.getContent(Exception.class);
+ // remove callback so that it won't be invoked twice
+ ClientCallback callback = m.getExchange().remove(ClientCallback.class);
+
+ if (callback != null) {
+ Map<String, Object> resCtx = CastUtils.cast((Map<?, ?>) m.getExchange().getOutMessage().get(
+ Message.INVOCATION_CONTEXT));
+ resCtx = CastUtils.cast((Map<?, ?>) resCtx.get(Client.RESPONSE_CONTEXT));
+ callback.handleException(resCtx, ex);
+ }
+ }
+
+ protected boolean isOutboundObserver() {
+ return true;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/interceptor/InFaultChainInitiatorObserver.java b/transform/src/patch/java/org/apache/cxf/interceptor/InFaultChainInitiatorObserver.java
new file mode 100644
index 0000000..fd35182
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/interceptor/InFaultChainInitiatorObserver.java
@@ -0,0 +1,84 @@
+/**
+ * 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.interceptor;
+
+import java.util.Collection;
+import java.util.SortedSet;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.endpoint.Client;
+import org.apache.cxf.endpoint.Endpoint;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.Phase;
+import org.apache.cxf.phase.PhaseInterceptorChain;
+import org.apache.cxf.phase.PhaseManager;
+
+public class InFaultChainInitiatorObserver extends AbstractFaultChainInitiatorObserver {
+
+ public InFaultChainInitiatorObserver(Bus bus) {
+ super(bus);
+ }
+
+ @Override
+ protected void initializeInterceptors(Exchange ex, PhaseInterceptorChain chain) {
+ Endpoint e = ex.getEndpoint();
+ Client c = ex.get(Client.class);
+ InterceptorProvider ip = ex.get(InterceptorProvider.class);
+
+ chain.add(getBus().getInFaultInterceptors());
+ if (c != null) {
+ chain.add(c.getInFaultInterceptors());
+ } else if (ip != null) {
+ chain.add(ip.getInFaultInterceptors());
+ }
+ chain.add(e.getService().getInFaultInterceptors());
+ chain.add(e.getInFaultInterceptors());
+ chain.add(e.getBinding().getInFaultInterceptors());
+ if (e.getService().getDataBinding() instanceof InterceptorProvider) {
+ chain.add(((InterceptorProvider)e.getService().getDataBinding()).getInFaultInterceptors());
+ }
+
+ addToChain(chain, ex.getInFaultMessage());
+ addToChain(chain, ex.getOutMessage());
+ }
+ private void addToChain(PhaseInterceptorChain chain, Message m) {
+ Collection<InterceptorProvider> providers
+ = CastUtils.cast((Collection<?>)m.get(Message.INTERCEPTOR_PROVIDERS));
+ if (providers != null) {
+ for (InterceptorProvider p : providers) {
+ chain.add(p.getInFaultInterceptors());
+ }
+ }
+ Collection<Interceptor<? extends Message>> is
+ = CastUtils.cast((Collection<?>)m.get(Message.FAULT_IN_INTERCEPTORS));
+ if (is != null) {
+ chain.add(is);
+ }
+ }
+
+ protected SortedSet<Phase> getPhases() {
+ return getBus().getExtension(PhaseManager.class).getInPhases();
+ }
+
+ protected boolean isOutboundObserver() {
+ return false;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/interceptor/InterceptorChain.java b/transform/src/patch/java/org/apache/cxf/interceptor/InterceptorChain.java
new file mode 100644
index 0000000..da5deba
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/interceptor/InterceptorChain.java
@@ -0,0 +1,109 @@
+/**
+ * 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.interceptor;
+
+import java.util.Collection;
+import java.util.ListIterator;
+
+import org.apache.cxf.message.Message;
+import org.apache.cxf.transport.MessageObserver;
+
+/**
+ * Base interface for all interceptor chains. An interceptor chain is an
+ * ordered list of interceptors associated with one portion of the message
+ * processing pipeline. Interceptor chains are defined for a client's request
+ * processing, response processing, and incoming SOAP fault processing. Interceptor
+ * chains are defined for a service's request processing, response processing, and
+ * outgoing SOAP fault processing.
+ */
+public interface InterceptorChain extends Iterable<Interceptor<? extends Message>> {
+
+ enum State {
+ PAUSED,
+ SUSPENDED,
+ EXECUTING,
+ COMPLETE,
+ ABORTED,
+ }
+
+ String STARTING_AFTER_INTERCEPTOR_ID = "starting_after_interceptor_id";
+ String STARTING_AT_INTERCEPTOR_ID = "starting_at_interceptor_id";
+
+ /**
+ * Adds a single interceptor to the interceptor chain.
+ *
+ * @param i the interceptor to add
+ */
+ void add(Interceptor<? extends Message> i);
+
+ /**
+ * Adds multiple interceptors to the interceptor chain.
+ * @param i the interceptors to add to the chain
+ */
+ void add(Collection<Interceptor<? extends Message>> i);
+
+ void remove(Interceptor<? extends Message> i);
+
+ boolean doIntercept(Message message);
+
+ boolean doInterceptStartingAfter(Message message, String startingAfterInterceptorID);
+
+ boolean doInterceptStartingAt(Message message, String startingAtInterceptorID);
+
+ /**
+ * Pauses the current chain. When the stack unwinds, the chain will just
+ * return from the doIntercept method normally.
+ */
+ void pause();
+
+ /**
+ * Suspends the current chain. When the stack unwinds, the chain back up
+ * the iterator by one (so on resume, the interceptor that called pause will
+ * be re-entered) and then throw a SuspendedInvocationException to the caller
+ */
+ void suspend();
+
+ /**
+ * Resumes the chain. The chain will use the current thread to continue processing
+ * the last message that was passed into doIntercept
+ */
+ void resume();
+
+ /**
+ * If the chain is marked as paused, this will JUST mark the chain as
+ * in the EXECUTING phase. This is useful if an interceptor pauses the chain,
+ * but then immediately decides it should not have done that. It can unpause
+ * the chain and return normally and the normal processing will continue.
+ */
+ void unpause();
+
+
+ void reset();
+
+ State getState();
+
+ ListIterator<Interceptor<? extends Message>> getIterator();
+
+ MessageObserver getFaultObserver();
+
+ void setFaultObserver(MessageObserver i);
+
+ void abort();
+}
diff --git a/transform/src/patch/java/org/apache/cxf/interceptor/LoggingMessage.java b/transform/src/patch/java/org/apache/cxf/interceptor/LoggingMessage.java
new file mode 100644
index 0000000..661e3db
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/interceptor/LoggingMessage.java
@@ -0,0 +1,133 @@
+/**
+ * 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.interceptor;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @deprecated use the logging module rt/features/logging instead
+ */
+@Deprecated
+public final class LoggingMessage {
+ public static final String ID_KEY = LoggingMessage.class.getName() + ".ID";
+ private static final AtomicInteger ID = new AtomicInteger();
+
+ private final String heading;
+ private final StringBuilder address;
+ private final StringBuilder contentType;
+ private final StringBuilder encoding;
+ private final StringBuilder httpMethod;
+ private final StringBuilder header;
+ private final StringBuilder message;
+ private final StringBuilder payload;
+ private final StringBuilder responseCode;
+ private final String id;
+
+
+ public LoggingMessage(String h, String i) {
+ heading = h;
+ id = i;
+
+ contentType = new StringBuilder();
+ address = new StringBuilder();
+ encoding = new StringBuilder();
+ httpMethod = new StringBuilder();
+ header = new StringBuilder();
+ message = new StringBuilder();
+ payload = new StringBuilder();
+ responseCode = new StringBuilder();
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public static String nextId() {
+ return Integer.toString(ID.incrementAndGet());
+ }
+
+
+ public StringBuilder getAddress() {
+ return address;
+ }
+
+ public StringBuilder getEncoding() {
+ return encoding;
+ }
+
+ public StringBuilder getHeader() {
+ return header;
+ }
+
+ public StringBuilder getHttpMethod() {
+ return httpMethod;
+ }
+
+ public StringBuilder getContentType() {
+ return contentType;
+ }
+
+ public StringBuilder getMessage() {
+ return message;
+ }
+
+ public StringBuilder getPayload() {
+ return payload;
+ }
+
+ public StringBuilder getResponseCode() {
+ return responseCode;
+ }
+
+ public String toString() {
+ StringBuilder buffer = new StringBuilder(128);
+ buffer.append(heading);
+ buffer.append("\nID: ").append(id);
+ if (address.length() > 0) {
+ buffer.append("\nAddress: ");
+ buffer.append(address);
+ }
+ if (responseCode.length() > 0) {
+ buffer.append("\nResponse-Code: ");
+ buffer.append(responseCode);
+ }
+ if (encoding.length() > 0) {
+ buffer.append("\nEncoding: ");
+ buffer.append(encoding);
+ }
+ if (httpMethod.length() > 0) {
+ buffer.append("\nHttp-Method: ");
+ buffer.append(httpMethod);
+ }
+ buffer.append("\nContent-Type: ");
+ buffer.append(contentType);
+ buffer.append("\nHeaders: ");
+ buffer.append(header);
+ if (message.length() > 0) {
+ buffer.append("\nMessages: ");
+ buffer.append(message);
+ }
+ if (payload.length() > 0) {
+ buffer.append("\nPayload: ");
+ buffer.append(payload);
+ }
+ buffer.append("\n--------------------------------------");
+ return buffer.toString();
+ }
+}
\ No newline at end of file
diff --git a/transform/src/patch/java/org/apache/cxf/interceptor/OneWayProcessorInterceptor.java b/transform/src/patch/java/org/apache/cxf/interceptor/OneWayProcessorInterceptor.java
new file mode 100644
index 0000000..b140601
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/interceptor/OneWayProcessorInterceptor.java
@@ -0,0 +1,177 @@
+/**
+ * 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.interceptor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.logging.Logger;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.endpoint.Endpoint;
+import org.apache.cxf.io.DelegatingInputStream;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageImpl;
+import org.apache.cxf.message.MessageUtils;
+import org.apache.cxf.phase.AbstractPhaseInterceptor;
+import org.apache.cxf.phase.Phase;
+import org.apache.cxf.transport.Conduit;
+import org.apache.cxf.workqueue.WorkQueueManager;
+
+
+/**
+ *
+ */
+public class OneWayProcessorInterceptor extends AbstractPhaseInterceptor<Message> {
+ public static final String USE_ORIGINAL_THREAD
+ = OneWayProcessorInterceptor.class.getName() + ".USE_ORIGINAL_THREAD";
+ private static final Logger LOG = LogUtils.getL7dLogger(OneWayProcessorInterceptor.class);
+
+ public OneWayProcessorInterceptor() {
+ super(Phase.PRE_LOGICAL);
+ }
+ public OneWayProcessorInterceptor(String phase) {
+ super(phase);
+ }
+
+ @Override
+ public void handleFault(Message message) {
+ if (message.getExchange().isOneWay()
+ && !isRequestor(message)) {
+ //in a one way, if an exception is thrown, the stream needs to be closed
+ InputStream in = message.getContent(InputStream.class);
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ //ignore
+ }
+ }
+
+ }
+ }
+ public void handleMessage(Message message) {
+
+ if (message.getExchange().isOneWay()
+ && !isRequestor(message)
+ && message.get(OneWayProcessorInterceptor.class) == null
+ && message.getExchange().get(Executor.class) == null) {
+ //one way on server side, fork the rest of this chain onto the
+ //workqueue, call the Outgoing chain directly.
+
+ message.put(OneWayProcessorInterceptor.class, this);
+ final InterceptorChain chain = message.getInterceptorChain();
+
+ boolean robust =
+ MessageUtils.getContextualBoolean(message, Message.ROBUST_ONEWAY, false);
+
+ boolean useOriginalThread =
+ MessageUtils.getContextualBoolean(message, USE_ORIGINAL_THREAD, false);
+
+ if (!useOriginalThread && !robust) {
+ //need to suck in all the data from the input stream as
+ //the transport might discard any data on the stream when this
+ //thread unwinds or when the empty response is sent back
+ DelegatingInputStream in = message.getContent(DelegatingInputStream.class);
+ if (in != null) {
+ in.cacheInput();
+ }
+ }
+
+ if (robust) {
+ // continue to invoke the chain
+ chain.pause();
+ chain.resume();
+ if (message.getContent(Exception.class) != null) {
+ // CXF-5629 fault has been delivered alread in resume()
+ return;
+ }
+ }
+
+ try {
+ Message partial = createMessage(message.getExchange());
+ partial.remove(Message.CONTENT_TYPE);
+ partial.setExchange(message.getExchange());
+ Conduit conduit = message.getExchange().getDestination()
+ .getBackChannel(message);
+ if (conduit != null) {
+ message.getExchange().setInMessage(null);
+ //for a one-way, the back channel could be
+ //null if it knows it cannot send anything.
+ conduit.prepare(partial);
+ conduit.close(partial);
+ message.getExchange().setInMessage(message);
+ }
+ } catch (IOException e) {
+ //IGNORE
+ }
+
+ if (!useOriginalThread && !robust) {
+ chain.pause();
+ try {
+ final Object lock = new Object();
+ synchronized (lock) {
+ message.getExchange().getBus().getExtension(WorkQueueManager.class)
+ .getAutomaticWorkQueue().execute(new Runnable() {
+ public void run() {
+ synchronized (lock) {
+ lock.notifyAll();
+ }
+ chain.resume();
+ }
+ });
+ //wait a few milliseconds for the background thread to start processing
+ //Mostly just to make an attempt at keeping the ordering of the
+ //messages coming in from a client. Not guaranteed though.
+ lock.wait(20L);
+ }
+ } catch (RejectedExecutionException e) {
+ LOG.warning(
+ "Executor queue is full, run the oneway invocation task in caller thread."
+ + " Users can specify a larger executor queue to avoid this.");
+ // only block the thread if the prop is unset or set to false, otherwise let it go
+ if (!MessageUtils.getContextualBoolean(message,
+ "org.apache.cxf.oneway.rejected_execution_exception", false)) {
+ //the executor queue is full, so run the task in the caller thread
+ chain.unpause();
+ }
+
+ } catch (InterruptedException e) {
+ //ignore - likely a busy work queue so we'll just let the one-way go
+ }
+ }
+ }
+ }
+
+ private static Message createMessage(Exchange exchange) {
+ Endpoint ep = exchange.getEndpoint();
+ Message msg = null;
+ if (ep != null) {
+ msg = new MessageImpl();
+ msg.setExchange(exchange);
+ msg = ep.getBinding().createMessage(msg);
+ }
+ return msg;
+ }
+
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/interceptor/OutFaultChainInitiatorObserver.java b/transform/src/patch/java/org/apache/cxf/interceptor/OutFaultChainInitiatorObserver.java
new file mode 100644
index 0000000..a00e403
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/interceptor/OutFaultChainInitiatorObserver.java
@@ -0,0 +1,84 @@
+/**
+ * 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.interceptor;
+
+import java.util.Collection;
+import java.util.SortedSet;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.endpoint.Client;
+import org.apache.cxf.endpoint.Endpoint;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.Phase;
+import org.apache.cxf.phase.PhaseInterceptorChain;
+import org.apache.cxf.phase.PhaseManager;
+
+public class OutFaultChainInitiatorObserver extends AbstractFaultChainInitiatorObserver {
+
+ public OutFaultChainInitiatorObserver(Bus bus) {
+ super(bus);
+ }
+
+ @Override
+ protected void initializeInterceptors(Exchange ex, PhaseInterceptorChain chain) {
+ Endpoint e = ex.getEndpoint();
+ Client c = ex.get(Client.class);
+
+ chain.add(getBus().getOutFaultInterceptors());
+ if (c != null) {
+ chain.add(c.getOutFaultInterceptors());
+ }
+ chain.add(e.getService().getOutFaultInterceptors());
+ chain.add(e.getOutFaultInterceptors());
+ chain.add(e.getBinding().getOutFaultInterceptors());
+ if (e.getService().getDataBinding() instanceof InterceptorProvider) {
+ chain.add(((InterceptorProvider)e.getService().getDataBinding()).getOutFaultInterceptors());
+ }
+
+ addToChain(chain, ex.getInMessage());
+ addToChain(chain, ex.getOutFaultMessage());
+ }
+ private void addToChain(PhaseInterceptorChain chain, Message m) {
+ Collection<InterceptorProvider> providers
+ = CastUtils.cast((Collection<?>)m.get(Message.INTERCEPTOR_PROVIDERS));
+ if (providers != null) {
+ for (InterceptorProvider p : providers) {
+ chain.add(p.getOutFaultInterceptors());
+ }
+ }
+ Collection<Interceptor<? extends Message>> is
+ = CastUtils.cast((Collection<?>)m.get(Message.FAULT_OUT_INTERCEPTORS));
+ if (is != null) {
+ chain.add(is);
+ }
+ if (m.getDestination() instanceof InterceptorProvider) {
+ chain.add(((InterceptorProvider)m.getDestination()).getOutFaultInterceptors());
+ }
+ }
+
+ protected SortedSet<Phase> getPhases() {
+ return getBus().getExtension(PhaseManager.class).getOutPhases();
+ }
+
+ protected boolean isOutboundObserver() {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/transform/src/patch/java/org/apache/cxf/interceptor/security/DefaultSecurityContext.java b/transform/src/patch/java/org/apache/cxf/interceptor/security/DefaultSecurityContext.java
new file mode 100644
index 0000000..9b712f3
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/interceptor/security/DefaultSecurityContext.java
@@ -0,0 +1,188 @@
+/**
+ * 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.interceptor.security;
+
+
+import java.lang.reflect.Method;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.security.auth.Subject;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.security.GroupPrincipal;
+import org.apache.cxf.common.util.ReflectionUtil;
+import org.apache.cxf.security.LoginSecurityContext;
+
+/**
+ * SecurityContext which implements isUserInRole using the
+ * following approach : skip the first Subject principal, and then checks
+ * Groups the principal is a member of
+ */
+public class DefaultSecurityContext implements LoginSecurityContext {
+ private static final Logger LOG = LogUtils.getL7dLogger(DefaultSecurityContext.class);
+ private static Class<?> javaGroup;
+ private static Class<?> karafGroup;
+
+ private Principal p;
+ private Subject subject;
+
+ static {
+ try {
+ javaGroup = Class.forName("java.security.acl.Group");
+ } catch (Exception e) {
+ javaGroup = null;
+ }
+ try {
+ karafGroup = Class.forName("org.apache.karaf.jaas.boot.principal.Group");
+ } catch (Exception e) {
+ karafGroup = null;
+ }
+ }
+
+ public DefaultSecurityContext(Subject subject) {
+ this.p = findPrincipal(null, subject);
+ this.subject = subject;
+ }
+
+ public DefaultSecurityContext(String principalName, Subject subject) {
+ this.p = findPrincipal(principalName, subject);
+ this.subject = subject;
+ }
+
+ public DefaultSecurityContext(Principal p, Subject subject) {
+ this.p = p;
+ this.subject = subject;
+ if (p == null) {
+ this.p = findPrincipal(null, subject);
+ }
+ }
+
+ private static Principal findPrincipal(String principalName, Subject subject) {
+ if (subject == null) {
+ return null;
+ }
+
+ for (Principal principal : subject.getPrincipals()) {
+ if (!isGroupPrincipal(principal)
+ && (principalName == null || principal.getName().equals(principalName))) {
+ return principal;
+ }
+ }
+
+ // No match for the principalName. Just return first non-Group Principal
+ if (principalName != null) {
+ for (Principal principal : subject.getPrincipals()) {
+ if (!isGroupPrincipal(principal)) {
+ return principal;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public Principal getUserPrincipal() {
+ return p;
+ }
+
+ public boolean isUserInRole(String role) {
+ if (subject != null) {
+ for (Principal principal : subject.getPrincipals()) {
+ if (isGroupPrincipal(principal)
+ && checkGroup(principal, role)) {
+ return true;
+ } else if (p != principal
+ && role.equals(principal.getName())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+
+ protected boolean checkGroup(Principal principal, String role) {
+ if (principal.getName().equals(role)) {
+ return true;
+ }
+
+ Enumeration<? extends Principal> members;
+ try {
+ Method m = ReflectionUtil.getMethod(principal.getClass(), "members");
+ m.setAccessible(true);
+ @SuppressWarnings("unchecked")
+ Enumeration<? extends Principal> ms = (Enumeration<? extends Principal>)m.invoke(principal);
+ members = ms;
+ } catch (Exception e) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Unable to invoke memebers in " + principal.getName() + ":" + e.getMessage());
+ }
+ return false;
+ }
+
+ while (members.hasMoreElements()) {
+ // this might be a plain role but could represent a group consisting of other groups/roles
+ Principal member = members.nextElement();
+ if (member.getName().equals(role)
+ || isGroupPrincipal(member)
+ && checkGroup((GroupPrincipal)member, role)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ public Subject getSubject() {
+ return subject;
+ }
+
+ public Set<Principal> getUserRoles() {
+ Set<Principal> roles = new HashSet<>();
+ if (subject != null) {
+ for (Principal principal : subject.getPrincipals()) {
+ if (principal != p) {
+ roles.add(principal);
+ }
+ }
+ }
+ return roles;
+ }
+
+
+ private static boolean instanceOfGroup(Object obj) {
+ try {
+ return (javaGroup != null && javaGroup.isInstance(obj))
+ || (karafGroup != null && karafGroup.isInstance(obj));
+ } catch (Exception ex) {
+ return false;
+ }
+ }
+
+ public static boolean isGroupPrincipal(Principal principal) {
+ return principal instanceof GroupPrincipal
+ || instanceOfGroup(principal);
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/interceptor/security/JAASLoginInterceptor.java b/transform/src/patch/java/org/apache/cxf/interceptor/security/JAASLoginInterceptor.java
new file mode 100644
index 0000000..3363c20
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/interceptor/security/JAASLoginInterceptor.java
@@ -0,0 +1,233 @@
+/**
+ * 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.interceptor.security;
+
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.interceptor.InterceptorChain;
+import org.apache.cxf.interceptor.security.callback.CallbackHandlerProvider;
+import org.apache.cxf.interceptor.security.callback.CallbackHandlerProviderAuthPol;
+import org.apache.cxf.interceptor.security.callback.CallbackHandlerProviderUsernameToken;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.AbstractPhaseInterceptor;
+import org.apache.cxf.phase.Phase;
+import org.apache.cxf.security.SecurityContext;
+
+public class JAASLoginInterceptor extends AbstractPhaseInterceptor<Message> {
+ public static final String ROLE_CLASSIFIER_PREFIX = "prefix";
+ public static final String ROLE_CLASSIFIER_CLASS_NAME = "classname";
+
+ private static final Logger LOG = LogUtils.getL7dLogger(JAASLoginInterceptor.class);
+
+ private String contextName = "";
+ private Configuration loginConfig;
+ private String roleClassifier;
+ private String roleClassifierType = ROLE_CLASSIFIER_PREFIX;
+ private boolean reportFault;
+ private boolean useDoAs = true;
+ private List<CallbackHandlerProvider> callbackHandlerProviders;
+ private boolean allowAnonymous = true;
+ private boolean allowNamedPrincipals;
+
+ public JAASLoginInterceptor() {
+ this(Phase.UNMARSHAL);
+ }
+
+ public JAASLoginInterceptor(String phase) {
+ super(phase);
+ this.callbackHandlerProviders = new ArrayList<>();
+ this.callbackHandlerProviders.add(new CallbackHandlerProviderAuthPol());
+ this.callbackHandlerProviders.add(new CallbackHandlerProviderUsernameToken());
+ }
+
+ public void setContextName(String name) {
+ contextName = name;
+ }
+
+ public String getContextName() {
+ return contextName;
+ }
+
+ /**
+ * @deprecated replaced by {@link #setRoleClassifier(String)}
+ * @param name
+ */
+ @Deprecated
+ public void setRolePrefix(String name) {
+ setRoleClassifier(name);
+ }
+
+ public void setRoleClassifier(String value) {
+ roleClassifier = value;
+ }
+
+ public String getRoleClassifier() {
+ return roleClassifier;
+ }
+
+ public void setRoleClassifierType(String value) {
+ if (!ROLE_CLASSIFIER_PREFIX.equals(value)
+ && !ROLE_CLASSIFIER_CLASS_NAME.equals(value)) {
+ throw new IllegalArgumentException("Unsupported role classifier");
+ }
+ roleClassifierType = value;
+ }
+
+ public String getRoleClassifierType() {
+ return roleClassifierType;
+ }
+
+ public void setReportFault(boolean reportFault) {
+ this.reportFault = reportFault;
+ }
+
+ public void setUseDoAs(boolean useDoAs) {
+ this.useDoAs = useDoAs;
+ }
+
+ private CallbackHandler getFirstCallbackHandler(Message message) {
+ for (CallbackHandlerProvider cbp : callbackHandlerProviders) {
+ CallbackHandler cbh = cbp.create(message);
+ if (cbh != null) {
+ return cbh;
+ }
+ }
+ return null;
+ }
+
+ public void handleMessage(final Message message) {
+ if (allowNamedPrincipals) {
+ SecurityContext sc = message.get(SecurityContext.class);
+ if (sc != null && sc.getUserPrincipal() != null
+ && sc.getUserPrincipal().getName() != null) {
+ return;
+ }
+ }
+
+ CallbackHandler handler = getFirstCallbackHandler(message);
+
+ if (handler == null && !allowAnonymous) {
+ throw new AuthenticationException("Authentication required but no authentication information was supplied");
+ }
+
+ try {
+ LoginContext ctx = new LoginContext(getContextName(), null, handler, loginConfig);
+ ctx.login();
+ Subject subject = ctx.getSubject();
+ String name = getUsername(handler);
+ message.put(SecurityContext.class, createSecurityContext(name, subject));
+
+ // Run the further chain in the context of this subject.
+ // This allows other code to retrieve the subject using pure JAAS
+ if (useDoAs) {
+ Subject.doAs(subject, new PrivilegedAction<Void>() {
+
+ @Override
+ public Void run() {
+ InterceptorChain chain = message.getInterceptorChain();
+ if (chain != null) {
+ message.put("suspend.chain.on.current.interceptor", Boolean.TRUE);
+ chain.doIntercept(message);
+ }
+ return null;
+ }
+ });
+ }
+
+ } catch (LoginException ex) {
+ String errorMessage = "Authentication failed: " + ex.getMessage();
+ LOG.log(Level.FINE, errorMessage, ex);
+ if (reportFault) {
+ AuthenticationException aex = new AuthenticationException(errorMessage);
+ aex.initCause(ex);
+ throw aex;
+
+ }
+ throw new AuthenticationException("Authentication failed (details can be found in server log)");
+ }
+ }
+
+ private String getUsername(CallbackHandler handler) {
+ if (handler == null) {
+ return null;
+ }
+ try {
+ NameCallback usernameCallBack = new NameCallback("user");
+ handler.handle(new Callback[]{usernameCallBack });
+ return usernameCallBack.getName();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ protected CallbackHandler getCallbackHandler(String name, String password) {
+ return new NamePasswordCallbackHandler(name, password);
+ }
+
+ protected SecurityContext createSecurityContext(String name, Subject subject) {
+ if (getRoleClassifier() != null) {
+ return new RolePrefixSecurityContextImpl(subject, getRoleClassifier(),
+ getRoleClassifierType());
+ }
+ return new DefaultSecurityContext(name, subject);
+ }
+
+ public Configuration getLoginConfig() {
+ return loginConfig;
+ }
+
+ public void setLoginConfig(Configuration loginConfig) {
+ this.loginConfig = loginConfig;
+ }
+
+ public List<CallbackHandlerProvider> getCallbackHandlerProviders() {
+ return callbackHandlerProviders;
+ }
+
+ public void setCallbackHandlerProviders(List<CallbackHandlerProvider> callbackHandlerProviders) {
+ this.callbackHandlerProviders.clear();
+ this.callbackHandlerProviders.addAll(callbackHandlerProviders);
+ }
+
+ public void addCallbackHandlerProviders(List<CallbackHandlerProvider> callbackHandlerProviders2) {
+ this.callbackHandlerProviders.addAll(callbackHandlerProviders2);
+ }
+
+ public void setAllowAnonymous(boolean allowAnonymous) {
+ this.allowAnonymous = allowAnonymous;
+ }
+
+ public void setAllowNamedPrincipals(boolean allowNamedPrincipals) {
+ this.allowNamedPrincipals = allowNamedPrincipals;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/internal/CXFAPINamespaceHandler.java b/transform/src/patch/java/org/apache/cxf/internal/CXFAPINamespaceHandler.java
new file mode 100644
index 0000000..e3d432d
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/internal/CXFAPINamespaceHandler.java
@@ -0,0 +1,127 @@
+/**
+ * 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.internal;
+
+import java.net.URL;
+import java.util.Set;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import org.apache.aries.blueprint.NamespaceHandler;
+import org.apache.aries.blueprint.Namespaces;
+import org.apache.aries.blueprint.ParserContext;
+import org.apache.aries.blueprint.mutable.MutableBeanMetadata;
+import org.apache.cxf.bus.blueprint.BusDefinitionParser;
+import org.apache.cxf.configuration.blueprint.SimpleBPBeanDefinitionParser;
+import org.apache.cxf.feature.FastInfosetFeature;
+import org.apache.cxf.workqueue.AutomaticWorkQueueImpl;
+import org.osgi.service.blueprint.reflect.ComponentMetadata;
+import org.osgi.service.blueprint.reflect.Metadata;
+
+@Namespaces({"http://cxf.apache.org/blueprint/core",
+ "http://cxf.apache.org/configuration/beans",
+ "http://cxf.apache.org/configuration/parameterized-types",
+ "http://cxf.apache.org/configuration/security",
+ "http://schemas.xmlsoap.org/wsdl/",
+ "http://www.w3.org/2005/08/addressing",
+ "http://schemas.xmlsoap.org/ws/2004/08/addressing"})
+public class CXFAPINamespaceHandler implements NamespaceHandler {
+
+ public URL getSchemaLocation(String namespace) {
+ String location = null;
+
+ // when schema is being resolved for custom namespace elements, "namespace" is real namespace
+ // (from xmlns:prefix="<namespace>"
+ // but when namespace is <xsd:import>ed, aries/xerces uses systemID (schemaLocation)
+
+ if ("http://cxf.apache.org/configuration/beans".equals(namespace)
+ || "http://cxf.apache.org/schemas/configuration/cxf-beans.xsd".equals(namespace)) {
+ location = "schemas/configuration/cxf-beans.xsd";
+ } else if ("http://cxf.apache.org/configuration/parameterized-types".equals(namespace)
+ || "http://cxf.apache.org/schemas/configuration/parameterized-types.xsd".equals(namespace)) {
+ location = "schemas/configuration/parameterized-types.xsd";
+ } else if ("http://cxf.apache.org/configuration/security".equals(namespace)
+ || "http://cxf.apache.org/schemas/configuration/security.xsd".equals(namespace)) {
+ location = "schemas/configuration/security.xsd";
+ } else if ("http://schemas.xmlsoap.org/wsdl/".equals(namespace)
+ || "http://schemas.xmlsoap.org/wsdl/2003-02-11.xsd".equals(namespace)) {
+ location = "schemas/wsdl/wsdl.xsd";
+ } else if ("http://www.w3.org/2005/08/addressing".equals(namespace)
+ || "http://www.w3.org/2006/03/addressing/ws-addr.xsd".equals(namespace)) {
+ location = "schemas/wsdl/ws-addr.xsd";
+ } else if ("http://schemas.xmlsoap.org/ws/2004/08/addressing".equals(namespace)) {
+ location = "schemas/wsdl/addressing.xsd";
+ } else if ("http://cxf.apache.org/blueprint/core".equals(namespace)) {
+ location = "schemas/blueprint/core.xsd";
+ }
+ if (location != null) {
+ return getClass().getClassLoader().getResource(location);
+ }
+ return null;
+ }
+
+
+ @SuppressWarnings("deprecation")
+ public Metadata parse(Element element, ParserContext context) {
+ String s = element.getLocalName();
+ if ("bus".equals(s)) {
+ //parse bus
+ return new BusDefinitionParser().parse(element, context);
+ } else if ("logging".equals(s)) {
+ //logging feature
+ return new SimpleBPBeanDefinitionParser(org.apache.cxf.feature.LoggingFeature.class)
+ .parse(element, context);
+ } else if ("fastinfoset".equals(s)) {
+ //fastinfosetfeature
+ return new SimpleBPBeanDefinitionParser(FastInfosetFeature.class).parse(element, context);
+ } else if ("workqueue".equals(s)) {
+ return new SimpleBPBeanDefinitionParser(AutomaticWorkQueueImpl.class) {
+
+ @Override
+ public String getId(Element element, ParserContext context) {
+ String id = element.hasAttribute("id") ? element.getAttribute("id") : null;
+ if (id == null) {
+ id = "cxf.workqueue.";
+ id += element.hasAttribute("name") ? element.getAttribute("name") : "def";
+ }
+ return id;
+ }
+
+ @Override
+ protected void processNameAttribute(Element element, ParserContext ctx,
+ MutableBeanMetadata bean, String val) {
+ bean.addProperty("name", createValue(ctx, val));
+ }
+ } .parse(element, context);
+ }
+ return null;
+ }
+
+ @SuppressWarnings("rawtypes")
+ public Set<Class> getManagedClasses() {
+ //probably should have the various stuff in cxf-api in here?
+ return null;
+ }
+ public ComponentMetadata decorate(Node node, ComponentMetadata component, ParserContext context) {
+ return null;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/io/CacheAndWriteOutputStream.java b/transform/src/patch/java/org/apache/cxf/io/CacheAndWriteOutputStream.java
new file mode 100644
index 0000000..d89edb9
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/io/CacheAndWriteOutputStream.java
@@ -0,0 +1,96 @@
+/**
+ * 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.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * This outputstream implementation will both write to the outputstream
+ * that is specified and cache the data at the same time. This allows us
+ * to go back and retransmit the data at a later time if necessary.
+ *
+ */
+public class CacheAndWriteOutputStream extends CachedOutputStream {
+ OutputStream flowThroughStream;
+ long count;
+ long limit = Long.MAX_VALUE;
+ private boolean isClosed;
+
+ public CacheAndWriteOutputStream(OutputStream stream) {
+ super();
+ if (stream == null) {
+ throw new IllegalArgumentException("Stream may not be null");
+ }
+ flowThroughStream = stream;
+ }
+
+ public void setCacheLimit(long l) {
+ limit = l;
+ }
+
+ public void closeFlowthroughStream() throws IOException {
+ postClose();
+ }
+
+ protected void postClose() throws IOException {
+ if (!isClosed) {
+ flowThroughStream.flush();
+ flowThroughStream.close();
+ isClosed = true;
+ }
+ }
+
+ public OutputStream getFlowThroughStream() {
+ return flowThroughStream;
+ }
+
+ @Override
+ protected void onWrite() throws IOException {
+ // does nothing
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ flowThroughStream.write(b);
+ if (count <= limit) {
+ super.write(b);
+ }
+ count++;
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ flowThroughStream.write(b, off, len);
+ if (count <= limit) {
+ super.write(b, off, len);
+ }
+ count += len;
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ flowThroughStream.write(b);
+ if (count <= limit) {
+ super.write(b);
+ }
+ count += b.length;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/io/CachedWriter.java b/transform/src/patch/java/org/apache/cxf/io/CachedWriter.java
new file mode 100644
index 0000000..0178563
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/io/CachedWriter.java
@@ -0,0 +1,665 @@
+/**
+ * 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.io;
+
+import java.io.BufferedOutputStream;
+import java.io.CharArrayReader;
+import java.io.CharArrayWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.common.util.SystemPropertyAction;
+import org.apache.cxf.helpers.FileUtils;
+import org.apache.cxf.helpers.IOUtils;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public class CachedWriter extends Writer {
+ private static final File DEFAULT_TEMP_DIR;
+ private static int defaultThreshold;
+ private static long defaultMaxSize;
+ private static String defaultCipherTransformation;
+
+ static {
+
+ String s = SystemPropertyAction.getPropertyOrNull(CachedConstants.OUTPUT_DIRECTORY_SYS_PROP);
+ if (s == null) {
+ // lookup the deprecated property
+ s = SystemPropertyAction.getPropertyOrNull("org.apache.cxf.io.CachedWriter.OutputDirectory");
+ }
+ if (s != null) {
+ File f = new File(s);
+ if (f.exists() && f.isDirectory()) {
+ DEFAULT_TEMP_DIR = f;
+ } else {
+ DEFAULT_TEMP_DIR = null;
+ }
+ } else {
+ DEFAULT_TEMP_DIR = null;
+ }
+
+ setDefaultThreshold(-1);
+ setDefaultMaxSize(-1);
+ setDefaultCipherTransformation(null);
+ }
+
+ protected boolean outputLocked;
+ protected Writer currentStream;
+
+ private boolean cosClosed;
+ private long threshold = defaultThreshold;
+ private long maxSize = defaultMaxSize;
+ private File outputDir = DEFAULT_TEMP_DIR;
+ private String cipherTransformation = defaultCipherTransformation;
+
+ private long totalLength;
+
+ private boolean inmem;
+
+ private boolean tempFileFailed;
+ private File tempFile;
+ private boolean allowDeleteOfFile = true;
+ private CipherPair ciphers;
+
+ private List<CachedWriterCallback> callbacks;
+
+ private List<Object> streamList = new ArrayList<>();
+
+
+ static class LoadingCharArrayWriter extends CharArrayWriter {
+ LoadingCharArrayWriter() {
+ super(1024);
+ }
+ public char[] rawCharArray() {
+ return super.buf;
+ }
+ }
+
+
+ public CachedWriter() {
+ this(defaultThreshold);
+
+ inmem = true;
+ }
+
+ public CachedWriter(long threshold) {
+ this.threshold = threshold;
+ currentStream = new LoadingCharArrayWriter();
+ inmem = true;
+ readBusProperties();
+ }
+
+ private void readBusProperties() {
+ Bus b = BusFactory.getThreadDefaultBus(false);
+ if (b != null) {
+ String v = getBusProperty(b, CachedConstants.THRESHOLD_BUS_PROP, null);
+ if (v != null && threshold == defaultThreshold) {
+ threshold = Integer.parseInt(v);
+ }
+ v = getBusProperty(b, CachedConstants.MAX_SIZE_BUS_PROP, null);
+ if (v != null) {
+ maxSize = Integer.parseInt(v);
+ }
+ v = getBusProperty(b, CachedConstants.CIPHER_TRANSFORMATION_BUS_PROP, null);
+ if (v != null) {
+ cipherTransformation = v;
+ }
+ v = getBusProperty(b, CachedConstants.OUTPUT_DIRECTORY_BUS_PROP, null);
+ if (v != null) {
+ File f = new File(v);
+ if (f.exists() && f.isDirectory()) {
+ outputDir = f;
+ }
+ }
+ }
+ }
+
+ private static String getBusProperty(Bus b, String key, String dflt) {
+ String v = (String)b.getProperty(key);
+ return v != null ? v : dflt;
+ }
+
+ public void holdTempFile() {
+ allowDeleteOfFile = false;
+ }
+ public void releaseTempFileHold() {
+ allowDeleteOfFile = true;
+ }
+
+ public void registerCallback(CachedWriterCallback cb) {
+ if (null == callbacks) {
+ callbacks = new ArrayList<>();
+ }
+ callbacks.add(cb);
+ }
+
+ public void deregisterCallback(CachedWriterCallback cb) {
+ if (null != callbacks) {
+ callbacks.remove(cb);
+ }
+ }
+
+ public List<CachedWriterCallback> getCallbacks() {
+ return callbacks == null ? null : Collections.unmodifiableList(callbacks);
+ }
+
+ /**
+ * Perform any actions required on stream flush (freeze headers, reset
+ * output stream ... etc.)
+ */
+ protected void doFlush() throws IOException {
+
+ }
+
+ public void flush() throws IOException {
+ if (!cosClosed) {
+ currentStream.flush();
+ }
+
+ if (null != callbacks) {
+ for (CachedWriterCallback cb : callbacks) {
+ cb.onFlush(this);
+ }
+ }
+ doFlush();
+ }
+
+ /**
+ * Perform any actions required on stream closure (handle response etc.)
+ */
+ protected void doClose() throws IOException {
+
+ }
+
+ /**
+ * Perform any actions required after stream closure (close the other related stream etc.)
+ */
+ protected void postClose() throws IOException {
+
+ }
+
+ /**
+ * Locks the output stream to prevent additional writes, but maintains
+ * a pointer to it so an InputStream can be obtained
+ * @throws IOException
+ */
+ public void lockOutputStream() throws IOException {
+ if (outputLocked) {
+ return;
+ }
+ currentStream.flush();
+ outputLocked = true;
+ if (null != callbacks) {
+ for (CachedWriterCallback cb : callbacks) {
+ cb.onClose(this);
+ }
+ }
+ doClose();
+ streamList.remove(currentStream);
+ }
+
+ public void close() throws IOException {
+ if (!cosClosed) {
+ currentStream.flush();
+ }
+ outputLocked = true;
+ if (null != callbacks) {
+ for (CachedWriterCallback cb : callbacks) {
+ cb.onClose(this);
+ }
+ }
+ doClose();
+ currentStream.close();
+ maybeDeleteTempFile(currentStream);
+ if (ciphers != null) {
+ ciphers.clean();
+ }
+ postClose();
+ }
+
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof CachedWriter) {
+ return currentStream.equals(((CachedWriter)obj).currentStream);
+ }
+ return currentStream.equals(obj);
+ }
+
+ /**
+ * Replace the original stream with the new one, optionally copying the content of the old one
+ * into the new one.
+ * When with Attachment, needs to replace the xml writer stream with the stream used by
+ * AttachmentSerializer or copy the cached output stream to the "real"
+ * output stream, i.e. onto the wire.
+ *
+ * @param out the new output stream
+ * @param copyOldContent flag indicating if the old content should be copied
+ * @throws IOException
+ */
+ public void resetOut(Writer out, boolean copyOldContent) throws IOException {
+ if (out == null) {
+ out = new LoadingCharArrayWriter();
+ }
+
+ if (currentStream instanceof CachedWriter) {
+ CachedWriter ac = (CachedWriter) currentStream;
+ Reader in = ac.getReader();
+ IOUtils.copyAndCloseInput(in, out);
+ } else {
+ if (inmem) {
+ if (currentStream instanceof LoadingCharArrayWriter) {
+ LoadingCharArrayWriter byteOut = (LoadingCharArrayWriter) currentStream;
+ if (copyOldContent && byteOut.size() > 0) {
+ byteOut.writeTo(out);
+ }
+ } else {
+ throw new IOException("Unknown format of currentStream");
+ }
+ } else {
+ // read the file
+ currentStream.close();
+ if (copyOldContent) {
+ InputStreamReader fin = createInputStreamReader(tempFile);
+ IOUtils.copyAndCloseInput(fin, out);
+ }
+ streamList.remove(currentStream);
+ deleteTempFile();
+ inmem = true;
+ }
+ }
+ currentStream = out;
+ outputLocked = false;
+ }
+
+
+ public long size() {
+ return totalLength;
+ }
+
+ public char[] getChars() throws IOException {
+ flush();
+ if (inmem) {
+ if (currentStream instanceof LoadingCharArrayWriter) {
+ return ((LoadingCharArrayWriter)currentStream).toCharArray();
+ }
+ throw new IOException("Unknown format of currentStream");
+ }
+ // read the file
+ try (Reader fin = createInputStreamReader(tempFile)) {
+ CharArrayWriter out = new CharArrayWriter((int)tempFile.length());
+ char[] bytes = new char[1024];
+ int x = fin.read(bytes);
+ while (x != -1) {
+ out.write(bytes, 0, x);
+ x = fin.read(bytes);
+ }
+ return out.toCharArray();
+ }
+ }
+
+ public void writeCacheTo(Writer out) throws IOException {
+ flush();
+ if (inmem) {
+ if (currentStream instanceof LoadingCharArrayWriter) {
+ ((LoadingCharArrayWriter)currentStream).writeTo(out);
+ } else {
+ throw new IOException("Unknown format of currentStream");
+ }
+ } else {
+ // read the file
+ try (Reader fin = createInputStreamReader(tempFile)) {
+ char[] bytes = new char[1024];
+ int x = fin.read(bytes);
+ while (x != -1) {
+ out.write(bytes, 0, x);
+ x = fin.read(bytes);
+ }
+ }
+ }
+ }
+
+ public void writeCacheTo(StringBuilder out, long limit) throws IOException {
+ flush();
+ if (totalLength < limit
+ || limit == -1) {
+ writeCacheTo(out);
+ return;
+ }
+
+ long count = 0;
+ if (inmem) {
+ if (currentStream instanceof LoadingCharArrayWriter) {
+ LoadingCharArrayWriter s = (LoadingCharArrayWriter)currentStream;
+ out.append(s.rawCharArray(), 0, (int)limit);
+ } else {
+ throw new IOException("Unknown format of currentStream");
+ }
+ } else {
+ // read the file
+ try (Reader fin = createInputStreamReader(tempFile)) {
+ char[] bytes = new char[1024];
+ long x = fin.read(bytes);
+ while (x != -1) {
+ if ((count + x) > limit) {
+ x = limit - count;
+ }
+ out.append(bytes, 0, (int)x);
+ count += x;
+
+ if (count >= limit) {
+ x = -1;
+ } else {
+ x = fin.read(bytes);
+ }
+ }
+ }
+ }
+ }
+
+ public void writeCacheTo(StringBuilder out) throws IOException {
+ flush();
+ if (inmem) {
+ if (currentStream instanceof LoadingCharArrayWriter) {
+ LoadingCharArrayWriter lcaw = (LoadingCharArrayWriter)currentStream;
+ out.append(lcaw.rawCharArray(), 0, lcaw.size());
+ } else {
+ throw new IOException("Unknown format of currentStream");
+ }
+ } else {
+ // read the file
+ try (Reader r = createInputStreamReader(tempFile)) {
+ char[] chars = new char[1024];
+ int x = r.read(chars);
+ while (x != -1) {
+ out.append(chars, 0, x);
+ x = r.read(chars);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * @return the underlying output stream
+ */
+ public Writer getOut() {
+ return currentStream;
+ }
+
+ public int hashCode() {
+ return currentStream.hashCode();
+ }
+
+ public String toString() {
+ StringBuilder builder = new StringBuilder().append('[')
+ .append(CachedWriter.class.getName())
+ .append(" Content: ");
+ try {
+ writeCacheTo(builder);
+ } catch (IOException e) {
+ //ignore
+ }
+ return builder.append(']').toString();
+ }
+
+ protected void onWrite() throws IOException {
+
+ }
+
+ private void enforceLimits() throws IOException {
+ if (maxSize > 0 && totalLength > maxSize) {
+ throw new CacheSizeExceededException();
+ }
+ if (inmem && totalLength > threshold && currentStream instanceof LoadingCharArrayWriter) {
+ createFileOutputStream();
+ }
+ }
+
+
+ public void write(char[] cbuf, int off, int len) throws IOException {
+ if (!outputLocked) {
+ onWrite();
+ this.totalLength += len;
+ enforceLimits();
+ currentStream.write(cbuf, off, len);
+ }
+ }
+
+ private void createFileOutputStream() throws IOException {
+ if (tempFileFailed) {
+ return;
+ }
+ LoadingCharArrayWriter bout = (LoadingCharArrayWriter)currentStream;
+ try {
+ if (outputDir == null) {
+ tempFile = FileUtils.createTempFile("cos", "tmp");
+ } else {
+ tempFile = FileUtils.createTempFile("cos", "tmp", outputDir, false);
+ }
+ currentStream = createOutputStreamWriter(tempFile);
+ bout.writeTo(currentStream);
+ inmem = false;
+ streamList.add(currentStream);
+ } catch (Exception ex) {
+ //Could be IOException or SecurityException or other issues.
+ //Don't care what, just keep it in memory.
+ tempFileFailed = true;
+ if (currentStream != bout) {
+ currentStream.close();
+ }
+ deleteTempFile();
+ inmem = true;
+ currentStream = bout;
+ }
+ }
+
+ public File getTempFile() {
+ return tempFile != null && tempFile.exists() ? tempFile : null;
+ }
+
+ public Reader getReader() throws IOException {
+ flush();
+ if (inmem) {
+ if (currentStream instanceof LoadingCharArrayWriter) {
+ LoadingCharArrayWriter lcaw = (LoadingCharArrayWriter)currentStream;
+ return new CharArrayReader(lcaw.rawCharArray(), 0, lcaw.size());
+ }
+ return null;
+ }
+ try {
+ InputStream fileInputStream = new FileInputStream(tempFile) {
+ boolean closed;
+
+ @Override
+ public void close() throws IOException {
+ if (!closed) {
+ super.close();
+ maybeDeleteTempFile(this);
+ }
+ closed = true;
+ }
+ };
+ streamList.add(fileInputStream);
+ if (cipherTransformation != null) {
+ fileInputStream = new CipherInputStream(fileInputStream, ciphers.getDecryptor()) {
+ boolean closed;
+
+ @Override
+ public void close() throws IOException {
+ if (!closed) {
+ super.close();
+ closed = true;
+ }
+ }
+ };
+ }
+ return new InputStreamReader(fileInputStream, StandardCharsets.UTF_8);
+ } catch (FileNotFoundException e) {
+ throw new IOException("Cached file was deleted, " + e.toString());
+ }
+ }
+
+ private synchronized void deleteTempFile() {
+ if (tempFile != null) {
+ File file = tempFile;
+ tempFile = null;
+ FileUtils.delete(file);
+ }
+ }
+ private void maybeDeleteTempFile(Object stream) {
+ streamList.remove(stream);
+ if (!inmem && tempFile != null && streamList.isEmpty() && allowDeleteOfFile) {
+ if (currentStream != null) {
+ try {
+ currentStream.close();
+ postClose();
+ } catch (Exception e) {
+ //ignore
+ }
+ }
+ deleteTempFile();
+ currentStream = new LoadingCharArrayWriter();
+ inmem = true;
+ }
+ }
+
+ public void setOutputDir(File outputDir) throws IOException {
+ this.outputDir = outputDir;
+ }
+ public void setThreshold(long threshold) {
+ this.threshold = threshold;
+ }
+
+ public void setMaxSize(long maxSize) {
+ this.maxSize = maxSize;
+ }
+
+ public void setCipherTransformation(String cipherTransformation) {
+ this.cipherTransformation = cipherTransformation;
+ }
+
+ public static void setDefaultMaxSize(long l) {
+ if (l == -1) {
+ String s = System.getProperty(CachedConstants.MAX_SIZE_SYS_PROP);
+ if (s == null) {
+ // lookup the deprecated property
+ s = System.getProperty("org.apache.cxf.io.CachedWriter.MaxSize", "-1");
+ }
+ l = Long.parseLong(s);
+ }
+ defaultMaxSize = l;
+ }
+
+ public static void setDefaultThreshold(int i) {
+ if (i == -1) {
+ i = SystemPropertyAction.getInteger(CachedConstants.THRESHOLD_SYS_PROP, -1);
+ if (i == -1) {
+ // lookup the deprecated property
+ i = SystemPropertyAction.getInteger("org.apache.cxf.io.CachedWriter.Threshold", -1);
+ }
+ if (i <= 0) {
+ i = 64 * 1024;
+ }
+ }
+ defaultThreshold = i;
+
+ }
+
+ public static void setDefaultCipherTransformation(String n) {
+ if (n == null) {
+ n = SystemPropertyAction.getPropertyOrNull(CachedConstants.CIPHER_TRANSFORMATION_SYS_PROP);
+ }
+ defaultCipherTransformation = n;
+ }
+
+ private OutputStreamWriter createOutputStreamWriter(File file) throws IOException {
+ OutputStream out = new BufferedOutputStream(Files.newOutputStream(file.toPath()));
+ if (cipherTransformation != null) {
+ try {
+ if (ciphers == null) {
+ ciphers = new CipherPair(cipherTransformation);
+ }
+ } catch (GeneralSecurityException e) {
+ out.close();
+ throw new IOException(e.getMessage(), e);
+ }
+ out = new CipherOutputStream(out, ciphers.getEncryptor()) {
+
+ @Override
+ public void close() throws IOException {
+ if (!cosClosed) {
+ super.close();
+ cosClosed = true;
+ }
+ }
+ };
+ }
+ return new OutputStreamWriter(out, UTF_8) {
+
+ @Override
+ public void close() throws IOException {
+ if (!cosClosed) {
+ super.close();
+ cosClosed = true;
+ }
+ }
+ };
+ }
+
+ private InputStreamReader createInputStreamReader(File file) throws IOException {
+ InputStream in = Files.newInputStream(file.toPath());
+ if (cipherTransformation != null) {
+ in = new CipherInputStream(in, ciphers.getDecryptor()) {
+ boolean closed;
+
+ @Override
+ public void close() throws IOException {
+ if (!closed) {
+ super.close();
+ closed = true;
+ }
+ }
+ };
+ }
+ return new InputStreamReader(in, UTF_8);
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/io/ReaderInputStream.java b/transform/src/patch/java/org/apache/cxf/io/ReaderInputStream.java
new file mode 100644
index 0000000..e95ed88
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/io/ReaderInputStream.java
@@ -0,0 +1,294 @@
+/**
+ * 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.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+
+/**
+ * {@link InputStream} implementation that reads a character stream from a {@link Reader}
+ * and transforms it to a byte stream using a specified charset encoding. The stream
+ * is transformed using a {@link CharsetEncoder} object, guaranteeing that all charset
+ * encodings supported by the JRE are handled correctly. In particular for charsets such as
+ * UTF-16, the implementation ensures that one and only one byte order marker
+ * is produced.
+ * <p>
+ * Since in general it is not possible to predict the number of characters to be read from the
+ * {@link Reader} to satisfy a read request on the {@link ReaderInputStream}, all reads from
+ * the {@link Reader} are buffered. There is therefore no well defined correlation
+ * between the current position of the {@link Reader} and that of the {@link ReaderInputStream}.
+ * This also implies that in general there is no need to wrap the underlying {@link Reader}
+ * in a {@link java.io.BufferedReader}.
+ * <p>
+ * {@link ReaderInputStream} implements the inverse transformation of {@link java.io.InputStreamReader};
+ * in the following example, reading from <tt>in2</tt> would return the same byte
+ * sequence as reading from <tt>in</tt> (provided that the initial byte sequence is legal
+ * with respect to the charset encoding):
+ * <pre>
+ * InputStream in = ...
+ * Charset cs = ...
+ * InputStreamReader reader = new InputStreamReader(in, cs);
+ * ReaderInputStream in2 = new ReaderInputStream(reader, cs);</pre>
+ * {@link ReaderInputStream} implements the same transformation as {@link java.io.OutputStreamWriter},
+ * except that the control flow is reversed: both classes transform a character stream
+ * into a byte stream, but {@link java.io.OutputStreamWriter} pushes data to the underlying stream,
+ * while {@link ReaderInputStream} pulls it from the underlying stream.
+ * <p>
+ * Note that while there are use cases where there is no alternative to using
+ * this class, very often the need to use this class is an indication of a flaw
+ * in the design of the code. This class is typically used in situations where an existing
+ * API only accepts an {@link InputStream}, but where the most natural way to produce the data
+ * is as a character stream, i.e. by providing a {@link Reader} instance. An example of a situation
+ * where this problem may appear is when implementing the {@link javax.activation.DataSource}
+ * interface from the Java Activation Framework.
+ * <p>
+ * Given the fact that the {@link Reader} class doesn't provide any way to predict whether the next
+ * read operation will block or not, it is not possible to provide a meaningful
+ * implementation of the {@link InputStream#available()} method. A call to this method
+ * will always return 0. Also, this class doesn't support {@link InputStream#mark(int)}.
+ * <p>
+ * Instances of {@link ReaderInputStream} are not thread safe.
+ *
+ * @since 2.0
+ */
+public class ReaderInputStream extends InputStream {
+ private static final int DEFAULT_BUFFER_SIZE = 1024;
+
+ private final Reader reader;
+ private final CharsetEncoder encoder;
+
+ /**
+ * CharBuffer used as input for the decoder. It should be reasonably
+ * large as we read data from the underlying Reader into this buffer.
+ */
+ private final CharBuffer encoderIn;
+
+ /**
+ * ByteBuffer used as output for the decoder. This buffer can be small
+ * as it is only used to transfer data from the decoder to the
+ * buffer provided by the caller.
+ */
+ private final ByteBuffer encoderOut;
+
+ private CoderResult lastCoderResult;
+ private boolean endOfInput;
+
+ /**
+ * Construct a new {@link ReaderInputStream}.
+ *
+ * @param reader the target {@link Reader}
+ * @param encoder the charset encoder
+ * @since 2.1
+ */
+ public ReaderInputStream(Reader reader, CharsetEncoder encoder) {
+ this(reader, encoder, DEFAULT_BUFFER_SIZE);
+ }
+
+ /**
+ * Construct a new {@link ReaderInputStream}.
+ *
+ * @param reader the target {@link Reader}
+ * @param encoder the charset encoder
+ * @param bufferSize the size of the input buffer in number of characters
+ * @since 2.1
+ */
+ public ReaderInputStream(Reader reader, CharsetEncoder encoder, int bufferSize) {
+ this.reader = reader;
+ this.encoder = encoder;
+ this.encoderIn = CharBuffer.allocate(bufferSize);
+ encoderIn.flip();
+ this.encoderOut = ByteBuffer.allocate(128);
+ encoderOut.flip();
+ }
+
+ /**
+ * Construct a new {@link ReaderInputStream}.
+ *
+ * @param reader the target {@link Reader}
+ * @param charset the charset encoding
+ * @param bufferSize the size of the input buffer in number of characters
+ */
+ public ReaderInputStream(Reader reader, Charset charset, int bufferSize) {
+ this(reader,
+ charset.newEncoder()
+ .onMalformedInput(CodingErrorAction.REPLACE)
+ .onUnmappableCharacter(CodingErrorAction.REPLACE),
+ bufferSize);
+ }
+
+ /**
+ * Construct a new {@link ReaderInputStream} with a default input buffer size of
+ * 1024 characters.
+ *
+ * @param reader the target {@link Reader}
+ * @param charset the charset encoding
+ */
+ public ReaderInputStream(Reader reader, Charset charset) {
+ this(reader, charset, DEFAULT_BUFFER_SIZE);
+ }
+
+ /**
+ * Construct a new {@link ReaderInputStream}.
+ *
+ * @param reader the target {@link Reader}
+ * @param charsetName the name of the charset encoding
+ * @param bufferSize the size of the input buffer in number of characters
+ */
+ public ReaderInputStream(Reader reader, String charsetName, int bufferSize) {
+ this(reader, Charset.forName(charsetName), bufferSize);
+ }
+
+ /**
+ * Construct a new {@link ReaderInputStream} with a default input buffer size of
+ * 1024 characters.
+ *
+ * @param reader the target {@link Reader}
+ * @param charsetName the name of the charset encoding
+ */
+ public ReaderInputStream(Reader reader, String charsetName) {
+ this(reader, charsetName, DEFAULT_BUFFER_SIZE);
+ }
+
+ /**
+ * Construct a new {@link ReaderInputStream} that uses the default character encoding
+ * with a default input buffer size of 1024 characters.
+ *
+ * @param reader the target {@link Reader}
+ */
+ public ReaderInputStream(Reader reader) {
+ this(reader, Charset.defaultCharset());
+ }
+
+ /**
+ * Fills the internal char buffer from the reader.
+ *
+ * @throws IOException
+ * If an I/O error occurs
+ */
+ private void fillBuffer() throws IOException {
+ if (!endOfInput && (lastCoderResult == null || lastCoderResult.isUnderflow())) {
+ encoderIn.compact();
+ int position = encoderIn.position();
+ // We don't use Reader#read(CharBuffer) here because it is more efficient
+ // to write directly to the underlying char array (the default implementation
+ // copies data to a temporary char array).
+ int c = reader.read(encoderIn.array(), position, encoderIn.remaining());
+ if (c == -1) {
+ endOfInput = true;
+ } else {
+ encoderIn.position(position + c);
+ }
+ encoderIn.flip();
+ }
+ encoderOut.compact();
+ lastCoderResult = encoder.encode(encoderIn, encoderOut, endOfInput);
+ encoderOut.flip();
+ }
+
+ /**
+ * Read the specified number of bytes into an array.
+ *
+ * @param b the byte array to read into
+ * @param off the offset to start reading bytes into
+ * @param len the number of bytes to read
+ * @return the number of bytes read or <code>-1</code>
+ * if the end of the stream has been reached
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (b == null) {
+ throw new NullPointerException("Byte array must not be null");
+ }
+ if (len < 0 || off < 0 || (off + len) > b.length) {
+ throw new IndexOutOfBoundsException("Array Size=" + b.length
+ + ", offset=" + off + ", length=" + len);
+ }
+ int read = 0;
+ if (len == 0) {
+ return 0; // Always return 0 if len == 0
+ }
+ while (len > 0) {
+ if (encoderOut.hasRemaining()) {
+ int c = Math.min(encoderOut.remaining(), len);
+ encoderOut.get(b, off, c);
+ off += c;
+ len -= c;
+ read += c;
+ } else {
+ fillBuffer();
+ if (endOfInput && !encoderOut.hasRemaining()) {
+ break;
+ }
+ }
+ }
+ return read == 0 && endOfInput ? -1 : read;
+ }
+
+ /**
+ * Read the specified number of bytes into an array.
+ *
+ * @param b the byte array to read into
+ * @return the number of bytes read or <code>-1</code>
+ * if the end of the stream has been reached
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ /**
+ * Read a single byte.
+ *
+ * @return either the byte read or <code>-1</code> if the end of the stream
+ * has been reached
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public int read() throws IOException {
+ for (;;) {
+ if (encoderOut.hasRemaining()) {
+ return encoderOut.get() & 0xFF;
+ } else {
+ fillBuffer();
+ if (endOfInput && !encoderOut.hasRemaining()) {
+ return -1;
+ }
+ }
+ }
+ }
+
+ /**
+ * Close the stream. This method will cause the underlying {@link Reader}
+ * to be closed.
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void close() throws IOException {
+ reader.close();
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/io/WriteOnCloseOutputStream.java b/transform/src/patch/java/org/apache/cxf/io/WriteOnCloseOutputStream.java
new file mode 100644
index 0000000..17f4579
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/io/WriteOnCloseOutputStream.java
@@ -0,0 +1,46 @@
+/**
+ * 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.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * This outputstream implementation will cache the message until close()
+ * is called, at which point it will write the message to the OutputStream
+ * supplied via the constructor.
+ */
+public class WriteOnCloseOutputStream extends CachedOutputStream {
+
+ OutputStream flowThroughStream;
+
+ public WriteOnCloseOutputStream(OutputStream stream) {
+ super();
+ flowThroughStream = stream;
+ }
+
+
+ @Override
+ protected void doClose() throws IOException {
+ resetOut(flowThroughStream, true);
+ flowThroughStream.flush();
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/JAXRSInvoker.java b/transform/src/patch/java/org/apache/cxf/jaxrs/JAXRSInvoker.java
new file mode 100644
index 0000000..163ff78
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/JAXRSInvoker.java
@@ -0,0 +1,460 @@
+/**
+ * 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.jaxrs;
+
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Collections;
+import java.util.List;
+import java.util.ResourceBundle;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CompletionStage;
+import java.util.logging.Logger;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.ResourceContext;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.classloader.ClassLoaderUtils.ClassLoaderHolder;
+import org.apache.cxf.common.i18n.BundleUtils;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.ClassHelper;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.interceptor.InterceptorChain.State;
+import org.apache.cxf.jaxrs.impl.AsyncResponseImpl;
+import org.apache.cxf.jaxrs.impl.MetadataMap;
+import org.apache.cxf.jaxrs.impl.ResourceContextImpl;
+import org.apache.cxf.jaxrs.lifecycle.ResourceProvider;
+import org.apache.cxf.jaxrs.model.ClassResourceInfo;
+import org.apache.cxf.jaxrs.model.OperationResourceInfo;
+import org.apache.cxf.jaxrs.model.ProviderInfo;
+import org.apache.cxf.jaxrs.model.URITemplate;
+import org.apache.cxf.jaxrs.provider.ServerProviderFactory;
+import org.apache.cxf.jaxrs.utils.ExceptionUtils;
+import org.apache.cxf.jaxrs.utils.InjectionUtils;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageContentsList;
+import org.apache.cxf.service.invoker.AbstractInvoker;
+
+public class JAXRSInvoker extends AbstractInvoker {
+ private static final Logger LOG = LogUtils.getL7dLogger(JAXRSInvoker.class);
+ private static final ResourceBundle BUNDLE = BundleUtils.getBundle(JAXRSInvoker.class);
+ private static final String SERVICE_LOADER_AS_CONTEXT = "org.apache.cxf.serviceloader-context";
+ private static final String SERVICE_OBJECT_SCOPE = "org.apache.cxf.service.scope";
+ private static final String REQUEST_SCOPE = "request";
+ private static final String LAST_SERVICE_OBJECT = "org.apache.cxf.service.object.last";
+ private static final String PROXY_INVOCATION_ERROR_FRAGMENT
+ = "object is not an instance of declaring class";
+
+ public JAXRSInvoker() {
+ }
+
+ public Object invoke(Exchange exchange, Object request) {
+ MessageContentsList responseList = checkExchangeForResponse(exchange);
+ if (responseList != null) {
+ return responseList;
+ }
+ AsyncResponse asyncResp = exchange.get(AsyncResponse.class);
+ if (asyncResp != null) {
+ AsyncResponseImpl asyncImpl = (AsyncResponseImpl)asyncResp;
+ asyncImpl.prepareContinuation();
+ try {
+ asyncImpl.handleTimeout();
+ return handleAsyncResponse(exchange, asyncImpl);
+ } catch (Throwable t) {
+ return handleAsyncFault(exchange, asyncImpl, t);
+ }
+ }
+
+ ResourceProvider provider = getResourceProvider(exchange);
+ Object rootInstance = null;
+ Message inMessage = exchange.getInMessage();
+ try {
+ rootInstance = getServiceObject(exchange);
+ Object serviceObject = getActualServiceObject(exchange, rootInstance);
+
+ return invoke(exchange, request, serviceObject);
+ } catch (WebApplicationException ex) {
+ responseList = checkExchangeForResponse(exchange);
+ if (responseList != null) {
+ return responseList;
+ }
+ return handleFault(ex, inMessage);
+ } finally {
+ boolean suspended = isSuspended(exchange);
+ if (suspended || exchange.isOneWay() || inMessage.get(Message.THREAD_CONTEXT_SWITCHED) != null) {
+ ServerProviderFactory.clearThreadLocalProxies(inMessage);
+ }
+ if (suspended || isServiceObjectRequestScope(inMessage)) {
+ persistRoots(exchange, rootInstance, provider);
+ } else {
+ provider.releaseInstance(inMessage, rootInstance);
+ }
+ }
+ }
+
+ private boolean isSuspended(Exchange exchange) {
+ return exchange.getInMessage().getInterceptorChain().getState() == State.SUSPENDED;
+ }
+
+ private Object handleAsyncResponse(Exchange exchange, AsyncResponseImpl ar) {
+ Object asyncObj = ar.getResponseObject();
+ if (asyncObj instanceof Throwable) {
+ final Throwable throwable = (Throwable)asyncObj;
+ Throwable cause = throwable;
+
+ if (throwable instanceof CompletionException) {
+ cause = throwable.getCause();
+ }
+
+ return handleAsyncFault(exchange, ar, (cause != null) ? cause : throwable);
+ }
+ setResponseContentTypeIfNeeded(exchange.getInMessage(), asyncObj);
+ return new MessageContentsList(asyncObj);
+ }
+
+ private Object handleAsyncFault(Exchange exchange, AsyncResponseImpl ar, Throwable t) {
+ try {
+ return handleFault(new Fault(t), exchange.getInMessage(), null, null);
+ } catch (Fault ex) {
+ ar.setUnmappedThrowable(ex.getCause() == null ? ex : ex.getCause());
+ if (isSuspended(exchange)) {
+ ar.reset();
+ exchange.getInMessage().getInterceptorChain().unpause();
+ }
+ return new MessageContentsList(Response.serverError().build());
+ }
+ }
+
+ private void persistRoots(Exchange exchange, Object rootInstance, Object provider) {
+ exchange.put(JAXRSUtils.ROOT_INSTANCE, rootInstance);
+ exchange.put(JAXRSUtils.ROOT_PROVIDER, provider);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Object invoke(Exchange exchange, Object request, Object resourceObject) {
+
+ final OperationResourceInfo ori = exchange.get(OperationResourceInfo.class);
+ final ClassResourceInfo cri = ori.getClassResourceInfo();
+ final Message inMessage = exchange.getInMessage();
+ final ServerProviderFactory providerFactory = ServerProviderFactory.getInstance(inMessage);
+ cri.injectContexts(resourceObject, ori, inMessage);
+
+ if (cri.isRoot()) {
+ ProviderInfo<Application> appProvider = providerFactory.getApplicationProvider();
+ if (appProvider != null) {
+ InjectionUtils.injectContexts(appProvider.getProvider(),
+ appProvider,
+ inMessage);
+ }
+ }
+
+
+ Method methodToInvoke = getMethodToInvoke(cri, ori, resourceObject);
+
+ List<Object> params = null;
+ if (request instanceof List) {
+ params = CastUtils.cast((List<?>)request);
+ } else if (request != null) {
+ params = new MessageContentsList(request);
+ }
+
+ Object result = null;
+ ClassLoaderHolder contextLoader = null;
+ AsyncResponseImpl asyncResponse = null;
+ try {
+ if (setServiceLoaderAsContextLoader(inMessage)) {
+ contextLoader = ClassLoaderUtils
+ .setThreadContextClassloader(resourceObject.getClass().getClassLoader());
+ }
+ if (!ori.isSubResourceLocator()) {
+ asyncResponse = (AsyncResponseImpl)inMessage.get(AsyncResponse.class);
+ }
+ result = invoke(exchange, resourceObject, methodToInvoke, params);
+ if (asyncResponse == null && !ori.isSubResourceLocator()) {
+ asyncResponse = checkFutureResponse(inMessage, checkResultObject(result));
+ }
+ if (asyncResponse != null) {
+ if (!asyncResponse.suspendContinuationIfNeeded()) {
+ result = handleAsyncResponse(exchange, asyncResponse);
+ } else {
+ providerFactory.clearThreadLocalProxies();
+ }
+ }
+ } catch (Fault ex) {
+ Object faultResponse;
+ if (asyncResponse != null) {
+ faultResponse = handleAsyncFault(exchange, asyncResponse,
+ ex.getCause() == null ? ex : ex.getCause());
+ } else {
+ faultResponse = handleFault(ex, inMessage, cri, methodToInvoke);
+ }
+ return faultResponse;
+ } finally {
+ exchange.put(LAST_SERVICE_OBJECT, resourceObject);
+ if (contextLoader != null) {
+ contextLoader.reset();
+ }
+ }
+ ClassResourceInfo subCri = null;
+ if (ori.isSubResourceLocator()) {
+ try {
+ MultivaluedMap<String, String> values = getTemplateValues(inMessage);
+ String subResourcePath = values.getFirst(URITemplate.FINAL_MATCH_GROUP);
+ String httpMethod = (String)inMessage.get(Message.HTTP_REQUEST_METHOD);
+ String contentType = (String)inMessage.get(Message.CONTENT_TYPE);
+ if (contentType == null) {
+ contentType = "*/*";
+ }
+ List<MediaType> acceptContentType =
+ (List<MediaType>)exchange.get(Message.ACCEPT_CONTENT_TYPE);
+
+ result = checkSubResultObject(result, subResourcePath);
+
+ final Class<?> subResponseType;
+ if (result.getClass() == Class.class) {
+ ResourceContext rc = new ResourceContextImpl(inMessage, ori);
+ result = rc.getResource((Class<?>)result);
+ subResponseType = InjectionUtils.getActualType(methodToInvoke.getGenericReturnType());
+ } else {
+ subResponseType = methodToInvoke.getReturnType();
+ }
+
+ subCri = cri.getSubResource(subResponseType,
+ ClassHelper.getRealClass(exchange.getBus(), result), result);
+ if (subCri == null) {
+ org.apache.cxf.common.i18n.Message errorM =
+ new org.apache.cxf.common.i18n.Message("NO_SUBRESOURCE_FOUND",
+ BUNDLE,
+ subResourcePath);
+ LOG.severe(errorM.toString());
+ throw ExceptionUtils.toNotFoundException(null, null);
+ }
+
+ OperationResourceInfo subOri = JAXRSUtils.findTargetMethod(
+ Collections.singletonMap(subCri, values),
+ inMessage,
+ httpMethod,
+ values,
+ contentType,
+ acceptContentType);
+ exchange.put(OperationResourceInfo.class, subOri);
+ inMessage.put(URITemplate.TEMPLATE_PARAMETERS, values);
+ inMessage.put(URITemplate.URI_TEMPLATE, JAXRSUtils.getUriTemplate(inMessage, subCri, ori, subOri));
+
+ if (!subOri.isSubResourceLocator()
+ && JAXRSUtils.runContainerRequestFilters(providerFactory,
+ inMessage,
+ false,
+ subOri.getNameBindings())) {
+ return new MessageContentsList(exchange.get(Response.class));
+ }
+
+ // work out request parameters for the sub-resource class. Here we
+ // presume InputStream has not been consumed yet by the root resource class.
+ List<Object> newParams = JAXRSUtils.processParameters(subOri, values, inMessage);
+ inMessage.setContent(List.class, newParams);
+
+ return this.invoke(exchange, newParams, result);
+ } catch (IOException ex) {
+ Response resp = JAXRSUtils.convertFaultToResponse(ex, inMessage);
+ if (resp == null) {
+ resp = JAXRSUtils.convertFaultToResponse(ex, inMessage);
+ }
+ return new MessageContentsList(resp);
+ } catch (WebApplicationException ex) {
+ Response excResponse;
+ if (JAXRSUtils.noResourceMethodForOptions(ex.getResponse(),
+ (String)inMessage.get(Message.HTTP_REQUEST_METHOD))) {
+ excResponse = JAXRSUtils.createResponse(Collections.singletonList(subCri),
+ null, null, 200, true);
+ } else {
+ excResponse = JAXRSUtils.convertFaultToResponse(ex, inMessage);
+ }
+ return new MessageContentsList(excResponse);
+ }
+ }
+ setResponseContentTypeIfNeeded(inMessage, result);
+ return result;
+ }
+
+ protected AsyncResponseImpl checkFutureResponse(Message inMessage, Object result) {
+ if (result instanceof CompletionStage) {
+ final CompletionStage<?> stage = (CompletionStage<?>)result;
+ final AsyncResponseImpl asyncResponse = new AsyncResponseImpl(inMessage);
+ stage.whenComplete((v, t) -> {
+ if (t instanceof CancellationException) {
+ asyncResponse.cancel();
+ } else {
+ asyncResponse.resume(v != null ? v : t);
+ }
+ });
+ return asyncResponse;
+ }
+ return null;
+ }
+
+ protected Method getMethodToInvoke(ClassResourceInfo cri, OperationResourceInfo ori, Object resourceObject) {
+ Method resourceMethod = cri.getMethodDispatcher().getMethod(ori);
+
+ Method methodToInvoke;
+ if (Proxy.class.isInstance(resourceObject)) {
+ methodToInvoke = cri.getMethodDispatcher().getProxyMethod(resourceMethod);
+ if (methodToInvoke == null) {
+ methodToInvoke = InjectionUtils.checkProxy(resourceMethod, resourceObject);
+ cri.getMethodDispatcher().addProxyMethod(resourceMethod, methodToInvoke);
+ }
+ } else {
+ methodToInvoke = resourceMethod;
+ }
+ return methodToInvoke;
+ }
+
+ private MessageContentsList checkExchangeForResponse(Exchange exchange) {
+ Response r = exchange.get(Response.class);
+ if (r != null) {
+ JAXRSUtils.setMessageContentType(exchange.getInMessage(), r);
+ return new MessageContentsList(r);
+ }
+ return null;
+ }
+
+ private void setResponseContentTypeIfNeeded(Message inMessage, Object response) {
+ if (response instanceof Response) {
+ JAXRSUtils.setMessageContentType(inMessage, (Response)response);
+ }
+ }
+ private Object handleFault(Throwable ex, Message inMessage) {
+ return handleFault(new Fault(ex), inMessage, null, null);
+ }
+ private Object handleFault(Fault ex, Message inMessage,
+ ClassResourceInfo cri, Method methodToInvoke) {
+ String errorMessage = ex.getMessage();
+ if (errorMessage != null && cri != null
+ && errorMessage.contains(PROXY_INVOCATION_ERROR_FRAGMENT)) {
+ org.apache.cxf.common.i18n.Message errorM =
+ new org.apache.cxf.common.i18n.Message("PROXY_INVOCATION_FAILURE",
+ BUNDLE,
+ methodToInvoke,
+ cri.getServiceClass().getName());
+ LOG.severe(errorM.toString());
+ }
+ Response excResponse =
+ JAXRSUtils.convertFaultToResponse(ex.getCause() == null ? ex : ex.getCause(), inMessage);
+ if (excResponse == null) {
+ inMessage.getExchange().put(Message.PROPOGATE_EXCEPTION,
+ ExceptionUtils.propogateException(inMessage));
+ throw ex;
+ }
+ return new MessageContentsList(excResponse);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected MultivaluedMap<String, String> getTemplateValues(Message msg) {
+ MultivaluedMap<String, String> values = new MetadataMap<>();
+ MultivaluedMap<String, String> oldValues =
+ (MultivaluedMap<String, String>)msg.get(URITemplate.TEMPLATE_PARAMETERS);
+ if (oldValues != null) {
+ values.putAll(oldValues);
+ }
+ return values;
+ }
+
+ private boolean setServiceLoaderAsContextLoader(Message inMessage) {
+ Object en = inMessage.getContextualProperty(SERVICE_LOADER_AS_CONTEXT);
+ return Boolean.TRUE.equals(en) || "true".equals(en);
+ }
+
+ private boolean isServiceObjectRequestScope(Message inMessage) {
+ Object scope = inMessage.getContextualProperty(SERVICE_OBJECT_SCOPE);
+ return REQUEST_SCOPE.equals(scope);
+ }
+
+ private ResourceProvider getResourceProvider(Exchange exchange) {
+ Object provider = exchange.remove(JAXRSUtils.ROOT_PROVIDER);
+ if (provider == null) {
+ OperationResourceInfo ori = exchange.get(OperationResourceInfo.class);
+ ClassResourceInfo cri = ori.getClassResourceInfo();
+ return cri.getResourceProvider();
+ }
+ return (ResourceProvider)provider;
+ }
+
+ public Object getServiceObject(Exchange exchange) {
+
+ Object root = exchange.remove(JAXRSUtils.ROOT_INSTANCE);
+ if (root != null) {
+ return root;
+ }
+
+ OperationResourceInfo ori = exchange.get(OperationResourceInfo.class);
+ ClassResourceInfo cri = ori.getClassResourceInfo();
+
+ return cri.getResourceProvider().getInstance(exchange.getInMessage());
+ }
+
+ protected Object getActualServiceObject(Exchange exchange, Object rootInstance) {
+
+ Object last = exchange.get(LAST_SERVICE_OBJECT);
+ return last != null ? last : rootInstance;
+ }
+
+
+
+ private static Object checkResultObject(Object result) {
+
+ if (result != null) {
+ if (result instanceof MessageContentsList) {
+ result = ((MessageContentsList)result).get(0);
+ } else if (result instanceof List) {
+ result = ((List<?>)result).get(0);
+ } else if (result.getClass().isArray()) {
+ result = ((Object[])result)[0];
+ }
+ }
+ return result;
+ }
+ private static Object checkSubResultObject(Object result, String subResourcePath) {
+ result = checkResultObject(result);
+ if (result == null) {
+ org.apache.cxf.common.i18n.Message errorM =
+ new org.apache.cxf.common.i18n.Message("NULL_SUBRESOURCE",
+ BUNDLE,
+ subResourcePath);
+ LOG.info(errorM.toString());
+ throw ExceptionUtils.toNotFoundException(null, null);
+ }
+
+ return result;
+ }
+
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/JAXRSServerFactoryBean.java b/transform/src/patch/java/org/apache/cxf/jaxrs/JAXRSServerFactoryBean.java
new file mode 100644
index 0000000..7871226
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/JAXRSServerFactoryBean.java
@@ -0,0 +1,467 @@
+/**
+ * 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.jaxrs;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.core.Application;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.classloader.ClassLoaderUtils.ClassLoaderHolder;
+import org.apache.cxf.endpoint.Endpoint;
+import org.apache.cxf.endpoint.Server;
+import org.apache.cxf.endpoint.ServerImpl;
+import org.apache.cxf.feature.Feature;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.jaxrs.ext.ResourceComparator;
+import org.apache.cxf.jaxrs.impl.RequestPreprocessor;
+import org.apache.cxf.jaxrs.lifecycle.PerRequestResourceProvider;
+import org.apache.cxf.jaxrs.lifecycle.ResourceProvider;
+import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
+import org.apache.cxf.jaxrs.model.ApplicationInfo;
+import org.apache.cxf.jaxrs.model.ClassResourceInfo;
+import org.apache.cxf.jaxrs.provider.ServerProviderFactory;
+import org.apache.cxf.jaxrs.utils.AnnotationUtils;
+import org.apache.cxf.jaxrs.utils.InjectionUtils;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.service.factory.FactoryBeanListener;
+import org.apache.cxf.service.factory.ServiceConstructionException;
+import org.apache.cxf.service.invoker.Invoker;
+
+
+/**
+ * Bean to help easily create Server endpoints for JAX-RS. Example:
+ * <pre>
+ * JAXRSServerFactoryBean sf = JAXRSServerFactoryBean();
+ * sf.setResourceClasses(Book.class);
+ * sf.setBindingId(JAXRSBindingFactory.JAXRS_BINDING_ID);
+ * sf.setAddress("http://localhost:9080/");
+ * Server myServer = sf.create();
+ * </pre>
+ * This will start a server for you and register it with the ServerManager. Note
+ * you should explicitly close the {@link org.apache.cxf.endpoint.Server} created
+ * when finished with it:
+ * <pre>
+ * myServer.close();
+ * myServer.destroy(); // closes first if close() not previously called
+ * </pre>
+ */
+public class JAXRSServerFactoryBean extends AbstractJAXRSFactoryBean {
+
+ protected Map<Class<?>, ResourceProvider> resourceProviders = new HashMap<>();
+
+ private Server server;
+ private boolean start = true;
+ private Map<Object, Object> languageMappings;
+ private Map<Object, Object> extensionMappings;
+ private ResourceComparator rc;
+ private ApplicationInfo appProvider;
+ private String documentLocation;
+
+ public JAXRSServerFactoryBean() {
+ this(new JAXRSServiceFactoryBean());
+ }
+
+ public JAXRSServerFactoryBean(JAXRSServiceFactoryBean sf) {
+ super(sf);
+ }
+
+ /**
+ * Saves the reference to the JAX-RS {@link Application}
+ * @param app
+ */
+ public void setApplication(Application app) {
+ setApplicationInfo(new ApplicationInfo(app, getBus()));
+ }
+
+ public void setApplicationInfo(ApplicationInfo provider) {
+ appProvider = provider;
+ Set<String> appNameBindings = AnnotationUtils.getNameBindings(bus, provider.getProvider().getClass());
+ for (ClassResourceInfo cri : getServiceFactory().getClassResourceInfo()) {
+ Set<String> clsNameBindings = new LinkedHashSet<>(appNameBindings);
+ clsNameBindings.addAll(AnnotationUtils.getNameBindings(bus, cri.getServiceClass()));
+ cri.setNameBindings(clsNameBindings);
+ }
+ }
+
+ /**
+ * Resource comparator which may be used to customize the way
+ * a root resource or resource method is selected
+ * @param rcomp comparator
+ */
+ public void setResourceComparator(ResourceComparator rcomp) {
+ rc = rcomp;
+ }
+
+ /**
+ * By default the subresources are resolved dynamically, mainly due to
+ * the JAX-RS specification allowing Objects being returned from the subresource
+ * locators. Setting this property to true enables the runtime to do the
+ * early resolution.
+ *
+ * @param enableStatic enabling the static resolution if set to true
+ */
+ public void setStaticSubresourceResolution(boolean enableStatic) {
+ serviceFactory.setEnableStaticResolution(enableStatic);
+ }
+
+
+ /**
+ * Creates the JAX-RS Server instance
+ * @return the server
+ */
+ public void init() {
+ if (server == null) {
+ create();
+ }
+ }
+
+ /**
+ * Creates the JAX-RS Server instance
+ * @return the server
+ */
+ public Server create() {
+ ClassLoaderHolder origLoader = null;
+ try {
+ Bus bus = getBus();
+ ClassLoader loader = bus.getExtension(ClassLoader.class);
+ if (loader != null) {
+ origLoader = ClassLoaderUtils.setThreadContextClassloader(loader);
+ }
+ serviceFactory.setBus(bus);
+ checkResources(true);
+ if (serviceFactory.getService() == null) {
+ serviceFactory.create();
+ }
+
+ Endpoint ep = createEndpoint();
+
+ getServiceFactory().sendEvent(FactoryBeanListener.Event.PRE_SERVER_CREATE,
+ server);
+
+ server = new ServerImpl(getBus(),
+ ep,
+ getDestinationFactory(),
+ getBindingFactory());
+
+ Invoker invoker = serviceFactory.getInvoker();
+ if (invoker == null) {
+ ep.getService().setInvoker(createInvoker());
+ } else {
+ ep.getService().setInvoker(invoker);
+ }
+
+ ServerProviderFactory factory = setupFactory(ep);
+
+ ep.put(Application.class.getName(), appProvider);
+ factory.setRequestPreprocessor(
+ new RequestPreprocessor(languageMappings, extensionMappings));
+ ep.put(Bus.class.getName(), getBus());
+ if (documentLocation != null) {
+ ep.put(JAXRSUtils.DOC_LOCATION, documentLocation);
+ }
+ if (rc != null) {
+ ep.put("org.apache.cxf.jaxrs.comparator", rc);
+ }
+ checkPrivateEndpoint(ep);
+
+ applyBusFeatures(getBus());
+ applyFeatures();
+
+ updateClassResourceProviders(ep);
+ injectContexts(factory, (ApplicationInfo)ep.get(Application.class.getName()));
+ factory.applyDynamicFeatures(getServiceFactory().getClassResourceInfo());
+
+
+ getServiceFactory().sendEvent(FactoryBeanListener.Event.SERVER_CREATED,
+ server,
+ null,
+ null);
+
+
+ if (start) {
+ try {
+ server.start();
+ } catch (RuntimeException re) {
+ if (!(re instanceof ServiceConstructionException
+ && re.getMessage().startsWith("There is an endpoint already running on"))) {
+ //avoid destroying another server on the same endpoint url
+ server.destroy(); // prevent resource leak if server really started by itself
+
+ }
+ throw re;
+ }
+ }
+ } catch (Exception e) {
+ throw new ServiceConstructionException(e);
+ } finally {
+ if (origLoader != null) {
+ origLoader.reset();
+ }
+ }
+
+ return server;
+ }
+
+ public Server getServer() {
+ return server;
+ }
+
+ protected ServerProviderFactory setupFactory(Endpoint ep) {
+ ServerProviderFactory factory = ServerProviderFactory.createInstance(getBus());
+ setBeanInfo(factory);
+ factory.setApplicationProvider(appProvider);
+ super.setupFactory(factory, ep);
+ return factory;
+ }
+
+ protected void setBeanInfo(ServerProviderFactory factory) {
+ List<ClassResourceInfo> cris = serviceFactory.getClassResourceInfo();
+ for (ClassResourceInfo cri : cris) {
+ cri.initBeanParamInfo(factory);
+ }
+
+ }
+
+ protected void applyBusFeatures(final Bus bus) {
+ if (bus.getFeatures() != null) {
+ for (Feature feature : bus.getFeatures()) {
+ feature.initialize(server, bus);
+ }
+ }
+ }
+
+ protected void applyFeatures() {
+ if (getFeatures() != null) {
+ for (Feature feature : getFeatures()) {
+ feature.initialize(server, getBus());
+ }
+ }
+ }
+
+ protected Invoker createInvoker() {
+ return serviceFactory.createInvoker();
+ }
+
+ /**
+ * Sets the language mappings,
+ * example, 'en' is the key and 'en-gb' is the value.
+ *
+ * @param lMaps the language mappings
+ */
+ public void setLanguageMappings(Map<Object, Object> lMaps) {
+ languageMappings = lMaps;
+ }
+
+ /**
+ * Sets the extension mappings,
+ * example, 'xml' is the key and 'text/xml' is the value.
+ *
+ * @param extMaps the extension mappings
+ */
+ public void setExtensionMappings(Map<Object, Object> extMaps) {
+ extensionMappings = extMaps;
+ }
+
+ public List<Class<?>> getResourceClasses() {
+ return serviceFactory.getResourceClasses();
+ }
+
+ /**
+ * This method is used primarily by Spring handler processing
+ * the jaxrs:server/@serviceClass attribute. It delegates to
+ * setResourceClasses method accepting the array of Class parameters.
+ * @param clazz the service/resource class
+ */
+ public void setServiceClass(Class<?> clazz) {
+ serviceFactory.setResourceClasses(clazz);
+ }
+
+ /**
+ * Sets one or more root resource classes
+ * @param classes the list of resource classes
+ */
+ public void setResourceClasses(List<Class<?>> classes) {
+ serviceFactory.setResourceClasses(classes);
+ }
+
+ /**
+ * Sets one or more root resource classes
+ * @param classes the array of resource classes
+ */
+ public void setResourceClasses(Class<?>... classes) {
+ serviceFactory.setResourceClasses(classes);
+ }
+
+ /**
+ * Sets the resource beans. If this is set then the JAX-RS runtime
+ * will not be responsible for the life-cycle of resource classes.
+ *
+ * @param beans the array of resource instances
+ */
+ public void setServiceBeanObjects(Object... beans) {
+ setServiceBeans(Arrays.asList(beans));
+ }
+
+ /**
+ * Sets the single resource bean. If this is set then the JAX-RS runtime
+ * will not be responsible for the life-cycle of resource classes.
+ * Please avoid setting the resource class of this bean explicitly,
+ * the runtime will determine it itself.
+ *
+ * @param bean resource instance
+ */
+ public void setServiceBean(Object bean) {
+ setServiceBeans(Arrays.asList(bean));
+ }
+
+ /**
+ * Sets the resource beans. If this is set then the JAX-RS runtime
+ * will not be responsible for the life-cycle of resource classes.
+ *
+ * @param beans the list of resource instances
+ */
+ public void setServiceBeans(List<Object> beans) {
+ List<Object> newBeans = new ArrayList<>();
+ addToBeans(newBeans, beans);
+ serviceFactory.setResourceClassesFromBeans(newBeans);
+ }
+
+ /**
+ * Sets the provider managing the life-cycle of the given resource class
+ * <pre>
+ * Example:
+ * setResourceProvider(BookStoreInterface.class, new SingletonResourceProvider(new BookStore()));
+ * </pre>
+ * @param c resource class
+ * @param rp resource provider
+ */
+ public void setResourceProvider(Class<?> c, ResourceProvider rp) {
+ resourceProviders.put(c, rp);
+ }
+
+ /**
+ * Sets the provider managing the life-cycle of the resource class
+ * <pre>
+ * Example:
+ * setResourceProvider(new SingletonResourceProvider(new BookStore()));
+ * </pre>
+ * @param rp resource provider
+ */
+ public void setResourceProvider(ResourceProvider rp) {
+ setResourceProviders(CastUtils.cast(Collections.singletonList(rp), ResourceProvider.class));
+ }
+
+ /**
+ * Sets the list of providers managing the life-cycle of the resource classes
+ *
+ * @param rps resource providers
+ */
+ public void setResourceProviders(List<ResourceProvider> rps) {
+ for (ResourceProvider rp : rps) {
+ Class<?> c = rp.getResourceClass();
+ setServiceClass(c);
+ resourceProviders.put(c, rp);
+ }
+ }
+
+ /**
+ * Sets the custom Invoker which can be used to customize the way
+ * the default JAX-RS invoker calls on the service bean
+ * @param invoker
+ */
+ public void setInvoker(Invoker invoker) {
+ serviceFactory.setInvoker(invoker);
+ }
+
+ /**
+ * Determines whether Services are automatically started during the create() call. Default is true.
+ * If false will need to call start() method on Server to activate it.
+ * @param start Whether (true) or not (false) to start the Server during Server creation.
+ */
+ public void setStart(boolean start) {
+ this.start = start;
+ }
+
+ protected void injectContexts(ServerProviderFactory factory, ApplicationInfo fallback) {
+ // Sometimes the application provider (ApplicationInfo) is injected through
+ // the endpoint, not JAXRSServerFactoryBean (like for example OpenApiFeature
+ // or Swagger2Feature do). As such, without consulting the endpoint, the injection
+ // may not work properly.
+ final ApplicationInfo appInfoProvider = (appProvider == null) ? fallback : appProvider;
+ final Application application = appInfoProvider == null ? null : appInfoProvider.getProvider();
+
+ for (ClassResourceInfo cri : serviceFactory.getClassResourceInfo()) {
+ if (cri.isSingleton()) {
+ InjectionUtils.injectContextProxiesAndApplication(cri,
+ cri.getResourceProvider().getInstance(null),
+ application,
+ factory);
+ }
+ }
+ if (application != null) {
+ InjectionUtils.injectContextProxiesAndApplication(appInfoProvider,
+ application, null, null);
+ }
+ }
+
+ protected void updateClassResourceProviders(Endpoint ep) {
+ for (ClassResourceInfo cri : serviceFactory.getClassResourceInfo()) {
+ if (cri.getResourceProvider() == null) {
+ ResourceProvider rp = resourceProviders.get(cri.getResourceClass());
+ if (rp != null) {
+ cri.setResourceProvider(rp);
+ } else {
+ setDefaultResourceProvider(cri);
+ }
+ }
+ if (cri.getResourceProvider() instanceof SingletonResourceProvider) {
+ ((SingletonResourceProvider)cri.getResourceProvider()).init(ep);
+ }
+ }
+ }
+
+ protected void setDefaultResourceProvider(ClassResourceInfo cri) {
+ cri.setResourceProvider(new PerRequestResourceProvider(cri.getResourceClass()));
+ }
+
+ /**
+ * Set the reference to the document (WADL, etc) describing the endpoint
+ * @param docLocation document location
+ */
+ public void setDocLocation(String docLocation) {
+ this.documentLocation = docLocation;
+ }
+
+ /**
+ * Get the reference to the document (WADL, etc) describing the endpoint
+ * @return document location
+ */
+ public String getDocLocation() {
+ return documentLocation;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/impl/EntityTagHeaderProvider.java b/transform/src/patch/java/org/apache/cxf/jaxrs/impl/EntityTagHeaderProvider.java
new file mode 100644
index 0000000..0deb1c3
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/impl/EntityTagHeaderProvider.java
@@ -0,0 +1,77 @@
+/**
+ * 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.jaxrs.impl;
+
+import javax.ws.rs.core.EntityTag;
+import javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate;
+
+public class EntityTagHeaderProvider implements HeaderDelegate<EntityTag> {
+
+ private static final String WEAK_PREFIX = "W/";
+
+ public EntityTag fromString(String header) {
+
+
+ if (header == null) {
+ throw new IllegalArgumentException("ETag value can not be null");
+ }
+
+ if ("*".equals(header)) {
+ return new EntityTag("*");
+ }
+
+ String tag;
+ boolean weak = false;
+ int i = header.indexOf(WEAK_PREFIX);
+ if (i != -1) {
+ weak = true;
+ if (i + 2 < header.length()) {
+ tag = header.substring(i + 2);
+ } else {
+ return new EntityTag("", weak);
+ }
+ } else {
+ tag = header;
+ }
+ if (tag.length() > 0 && !tag.startsWith("\"") && !tag.endsWith("\"")) {
+ return new EntityTag(tag, weak);
+ }
+ if (tag.length() < 2 || !tag.startsWith("\"") || !tag.endsWith("\"")) {
+ throw new IllegalArgumentException("Misformatted ETag : " + header);
+ }
+ tag = tag.length() == 2 ? "" : tag.substring(1, tag.length() - 1);
+ return new EntityTag(tag, weak);
+ }
+
+ public String toString(EntityTag tag) {
+ StringBuilder sb = new StringBuilder();
+ if (tag.isWeak()) {
+ sb.append(WEAK_PREFIX);
+ }
+ String tagValue = tag.getValue();
+ if (!tagValue.startsWith("\"")) {
+ sb.append('"').append(tagValue).append('"');
+ } else {
+ sb.append(tagValue);
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/impl/MediaTypeHeaderProvider.java b/transform/src/patch/java/org/apache/cxf/jaxrs/impl/MediaTypeHeaderProvider.java
new file mode 100644
index 0000000..a885a33
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/impl/MediaTypeHeaderProvider.java
@@ -0,0 +1,221 @@
+/**
+ * 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.jaxrs.impl;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.common.util.SystemPropertyAction;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageUtils;
+import org.apache.cxf.phase.PhaseInterceptorChain;
+
+public class MediaTypeHeaderProvider implements HeaderDelegate<MediaType> {
+ private static final Logger LOG = LogUtils.getL7dLogger(MediaTypeHeaderProvider.class);
+ private static final String STRICT_MEDIA_TYPE_CHECK =
+ "org.apache.cxf.jaxrs.mediaTypeCheck.strict";
+ private static final Pattern COMPLEX_PARAMETERS =
+ Pattern.compile("(([\\w-]+=\"[^\"]*\")|([\\w-]+=[\\w-/\\+]+))");
+
+ private static Map<String, MediaType> map = new ConcurrentHashMap<>();
+ private static final int MAX_MT_CACHE_SIZE =
+ SystemPropertyAction.getInteger("org.apache.cxf.jaxrs.max_mediatype_cache_size", 200);
+
+ public MediaType fromString(String mType) {
+
+ return valueOf(mType);
+ }
+
+ public static MediaType valueOf(String mType) {
+ if (mType == null) {
+ throw new IllegalArgumentException("Media type value can not be null");
+ }
+
+ MediaType result = map.get(mType);
+ if (result == null) {
+ result = internalValueOf(mType);
+ final int size = map.size();
+ if (size >= MAX_MT_CACHE_SIZE) {
+ map.clear();
+ }
+ map.put(mType, result);
+ }
+ return result;
+ }
+
+ public static MediaType internalValueOf(String mType) {
+
+ int i = mType.indexOf('/');
+ if (i == -1) {
+ return handleMediaTypeWithoutSubtype(mType.trim());
+ } else if (i == 0) {
+ throw new IllegalArgumentException("Invalid media type string: " + mType);
+ }
+
+ int paramsStart = mType.indexOf(';', i + 1);
+ int end = paramsStart == -1 ? mType.length() : paramsStart;
+
+ String type = mType.substring(0, i).trim();
+ String subtype = mType.substring(i + 1, end).trim();
+ if (!isValid(type) || !isValid(subtype)) {
+ throw new IllegalArgumentException("Invalid media type string: " + mType);
+ }
+
+ Map<String, String> parameters = Collections.emptyMap();
+ if (paramsStart != -1) {
+
+ parameters = new LinkedHashMap<>();
+
+ String paramString = mType.substring(paramsStart + 1);
+ if (paramString.contains("\"")) {
+ Matcher m = COMPLEX_PARAMETERS.matcher(paramString);
+ while (m.find()) {
+ String val = m.group().trim();
+ addParameter(parameters, val);
+ }
+ } else {
+ StringTokenizer st = new StringTokenizer(paramString, ";");
+ while (st.hasMoreTokens()) {
+ addParameter(parameters, st.nextToken());
+ }
+ }
+ }
+
+ return new MediaType(type.toLowerCase(),
+ subtype.toLowerCase(),
+ parameters);
+ }
+
+ private static void addParameter(Map<String, String> parameters, String token) {
+ int equalSign = token.indexOf('=');
+ if (equalSign == -1) {
+ throw new IllegalArgumentException("Wrong media type parameter, separator is missing");
+ }
+ parameters.put(token.substring(0, equalSign).trim().toLowerCase(),
+ token.substring(equalSign + 1).trim());
+ }
+
+ public String toString(MediaType type) {
+ return typeToString(type);
+ }
+ public static String typeToString(MediaType type) {
+ return typeToString(type, null);
+ }
+ // Max number of parameters that may be ignored is 3, at least as known
+ // to the implementation
+ public static String typeToString(MediaType type, List<String> ignoreParams) {
+ if (type == null) {
+ throw new IllegalArgumentException("MediaType parameter is null");
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.append(type.getType()).append('/').append(type.getSubtype());
+
+ Map<String, String> params = type.getParameters();
+ if (params != null) {
+ for (Map.Entry<String, String> entry : params.entrySet()) {
+ if (ignoreParams != null && ignoreParams.contains(entry.getKey())) {
+ continue;
+ }
+ sb.append(';').append(entry.getKey()).append('=').append(entry.getValue());
+ }
+ }
+
+ return sb.toString();
+ }
+
+ private static MediaType handleMediaTypeWithoutSubtype(String mType) {
+ if (mType.startsWith(MediaType.MEDIA_TYPE_WILDCARD)) {
+ String mTypeNext = mType.length() == 1 ? "" : mType.substring(1).trim();
+ boolean mTypeNextEmpty = StringUtils.isEmpty(mTypeNext);
+ if (mTypeNextEmpty || mTypeNext.startsWith(";")) {
+ if (!mTypeNextEmpty) {
+ Map<String, String> parameters = new LinkedHashMap<>();
+ StringTokenizer st = new StringTokenizer(mType.substring(2).trim(), ";");
+ while (st.hasMoreTokens()) {
+ addParameter(parameters, st.nextToken());
+ }
+ return new MediaType(MediaType.MEDIA_TYPE_WILDCARD,
+ MediaType.MEDIA_TYPE_WILDCARD,
+ parameters);
+ }
+ return MediaType.WILDCARD_TYPE;
+
+ }
+ }
+ Message message = PhaseInterceptorChain.getCurrentMessage();
+ if (message != null
+ && !MessageUtils.getContextualBoolean(message, STRICT_MEDIA_TYPE_CHECK, false)) {
+ final MediaType mt;
+ if (mType.equals(MediaType.TEXT_PLAIN_TYPE.getType())) {
+ mt = MediaType.TEXT_PLAIN_TYPE;
+ } else if (mType.equals(MediaType.APPLICATION_XML_TYPE.getSubtype())) {
+ mt = MediaType.APPLICATION_XML_TYPE;
+ } else {
+ mt = MediaType.WILDCARD_TYPE;
+ }
+ LOG.fine("Converting a malformed media type '" + mType + "' to '" + typeToString(mt) + "'");
+ return mt;
+ }
+ throw new IllegalArgumentException("Media type separator is missing");
+ }
+
+ // Determines whether the type or subtype contains any of the tspecials characters defined at:
+ // https://tools.ietf.org/html/rfc2045#section-5.1
+ private static boolean isValid(String str) {
+ final int len = str.length();
+ if (len == 0) {
+ return false;
+ }
+ for (int i = 0; i < len; i++) {
+ switch (str.charAt(i)) {
+ case '/':
+ case '\\':
+ case '?':
+ case ':':
+ case '<':
+ case '>':
+ case ';':
+ case '(':
+ case ')':
+ case '@':
+ case ',':
+ case '[':
+ case ']':
+ case '=':
+ return false;
+ default:
+ continue;
+ }
+ }
+ return true;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/impl/RequestImpl.java b/transform/src/patch/java/org/apache/cxf/jaxrs/impl/RequestImpl.java
new file mode 100644
index 0000000..5921f1a
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/impl/RequestImpl.java
@@ -0,0 +1,388 @@
+/**
+ * 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.jaxrs.impl;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.EntityTag;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.Variant;
+
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.jaxrs.utils.HttpUtils;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.PhaseInterceptorChain;
+
+/**
+ * TODO : deal with InvalidStateExceptions
+ *
+ */
+
+
+public class RequestImpl implements Request {
+ private final Message m;
+ private final HttpHeaders headers;
+
+ public RequestImpl(Message m) {
+ this.m = m;
+ this.headers = new HttpHeadersImpl(m);
+ }
+
+
+
+ public Variant selectVariant(List<Variant> vars) throws IllegalArgumentException {
+ if (vars == null || vars.isEmpty()) {
+ throw new IllegalArgumentException("List of Variants is either null or empty");
+ }
+ List<MediaType> acceptMediaTypes = headers.getAcceptableMediaTypes();
+ List<Locale> acceptLangs = headers.getAcceptableLanguages();
+ List<String> acceptEncs = parseAcceptEnc(
+ headers.getRequestHeaders().getFirst(HttpHeaders.ACCEPT_ENCODING));
+ List<Variant> requestVariants = sortAllCombinations(acceptMediaTypes, acceptLangs, acceptEncs);
+ List<Object> varyValues = new LinkedList<>();
+ for (Variant requestVar : requestVariants) {
+ for (Variant var : vars) {
+ MediaType mt = var.getMediaType();
+ Locale lang = var.getLanguage();
+ String enc = var.getEncoding();
+
+ boolean mtMatched = mt == null || requestVar.getMediaType().isCompatible(mt);
+ if (mtMatched) {
+ handleVaryValues(varyValues, HttpHeaders.ACCEPT);
+ }
+
+ boolean langMatched = lang == null || isLanguageMatched(requestVar.getLanguage(), lang);
+ if (langMatched) {
+ handleVaryValues(varyValues, HttpHeaders.ACCEPT_LANGUAGE);
+ }
+
+ boolean encMatched = acceptEncs.isEmpty() || enc == null
+ || isEncMatached(requestVar.getEncoding(), enc);
+ if (encMatched) {
+ handleVaryValues(varyValues, HttpHeaders.ACCEPT_ENCODING);
+ }
+
+ if (mtMatched && encMatched && langMatched) {
+ addVaryHeader(varyValues);
+ return var;
+ }
+ }
+ }
+ return null;
+ }
+
+ private static List<Variant> sortAllCombinations(List<MediaType> mediaTypes,
+ List<Locale> langs,
+ List<String> encs) {
+ List<Variant> requestVars = new LinkedList<>();
+ for (MediaType mt : mediaTypes) {
+ for (Locale lang : langs) {
+ if (encs.isEmpty()) {
+ requestVars.add(new Variant(mt, lang, null));
+ } else {
+ for (String enc : encs) {
+ requestVars.add(new Variant(mt, lang, enc));
+ }
+ }
+ }
+ }
+ Collections.sort(requestVars, VariantComparator.INSTANCE);
+ return requestVars;
+ }
+
+
+ private static void handleVaryValues(List<Object> varyValues, String ...values) {
+ for (String v : values) {
+ if (v != null && !varyValues.contains(v)) {
+ varyValues.add(v);
+ }
+ }
+ }
+
+ private static void addVaryHeader(List<Object> varyValues) {
+ // at this point we still have no out-bound message so lets
+ // use HttpServletResponse. If needed we can save the header on the exchange
+ // and then copy it into the out-bound message's headers
+ Message message = PhaseInterceptorChain.getCurrentMessage();
+ if (message != null) {
+ Object httpResponse = message.get("HTTP.RESPONSE");
+ if (httpResponse != null) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < varyValues.size(); i++) {
+ if (i > 0) {
+ sb.append(',');
+ }
+ sb.append(varyValues.get(i).toString());
+ }
+ ((javax.servlet.http.HttpServletResponse)httpResponse)
+ .setHeader(HttpHeaders.VARY, sb.toString());
+ }
+ }
+ }
+
+ private static boolean isLanguageMatched(Locale locale, Locale l) {
+
+ String language = locale.getLanguage();
+ return "*".equals(language)
+ || language.equalsIgnoreCase(l.getLanguage());
+ }
+
+ private static boolean isEncMatached(String accepts, String enc) {
+ return accepts == null || "*".equals(accepts) || accepts.contains(enc);
+ }
+
+ private static List<String> parseAcceptEnc(String acceptEnc) {
+ if (StringUtils.isEmpty(acceptEnc)) {
+ return Collections.emptyList();
+ }
+ List<String> list = new LinkedList<>();
+ String[] values = acceptEnc.split(",");
+ for (String value : values) {
+ String[] pair = value.trim().split(";");
+ // ignore encoding qualifiers if any for now
+ list.add(pair[0]);
+ }
+ return list;
+ }
+
+ public ResponseBuilder evaluatePreconditions(EntityTag eTag) {
+ if (eTag == null) {
+ throw new IllegalArgumentException("ETag is null");
+ }
+ return evaluateAll(eTag, null);
+ }
+
+ private ResponseBuilder evaluateAll(EntityTag eTag, Date lastModified) {
+ // http://tools.ietf.org/search/draft-ietf-httpbis-p4-conditional-25#section-5
+ // Check If-Match. If it is not available proceed to checking If-Not-Modified-Since
+ // if it is available and the preconditions are not met - return, otherwise:
+ // Check If-Not-Match. If it is not available proceed to checking If-Modified-Since
+ // otherwise return the evaluation result
+
+ ResponseBuilder rb = evaluateIfMatch(eTag, lastModified);
+ if (rb == null) {
+ rb = evaluateIfNonMatch(eTag, lastModified);
+ }
+ return rb;
+ }
+
+ private ResponseBuilder evaluateIfMatch(EntityTag eTag, Date date) {
+ List<String> ifMatch = headers.getRequestHeader(HttpHeaders.IF_MATCH);
+
+ if (ifMatch == null || ifMatch.isEmpty()) {
+ return date == null ? null : evaluateIfNotModifiedSince(date);
+ }
+
+ try {
+ for (String value : ifMatch) {
+ if ("*".equals(value)) {
+ return null;
+ }
+ EntityTag requestTag = EntityTag.valueOf(value);
+ // must be a strong comparison
+ if (!requestTag.isWeak() && !eTag.isWeak() && requestTag.equals(eTag)) {
+ return null;
+ }
+ }
+ } catch (IllegalArgumentException ex) {
+ // ignore
+ }
+ return Response.status(Response.Status.PRECONDITION_FAILED).tag(eTag);
+ }
+
+ private ResponseBuilder evaluateIfNonMatch(EntityTag eTag, Date lastModified) {
+ List<String> ifNonMatch = headers.getRequestHeader(HttpHeaders.IF_NONE_MATCH);
+
+ if (ifNonMatch == null || ifNonMatch.isEmpty()) {
+ return lastModified == null ? null : evaluateIfModifiedSince(lastModified);
+ }
+
+ String method = getMethod();
+ boolean getOrHead = HttpMethod.GET.equals(method) || HttpMethod.HEAD.equals(method);
+ try {
+ for (String value : ifNonMatch) {
+ boolean result = "*".equals(value);
+ if (!result) {
+ EntityTag requestTag = EntityTag.valueOf(value);
+ result = getOrHead ? requestTag.equals(eTag)
+ : !requestTag.isWeak() && !eTag.isWeak() && requestTag.equals(eTag);
+ }
+ if (result) {
+ Response.Status status = getOrHead ? Response.Status.NOT_MODIFIED
+ : Response.Status.PRECONDITION_FAILED;
+ return Response.status(status).tag(eTag);
+ }
+ }
+ } catch (IllegalArgumentException ex) {
+ // ignore
+ }
+ return null;
+ }
+
+ public ResponseBuilder evaluatePreconditions(Date lastModified) {
+ if (lastModified == null) {
+ throw new IllegalArgumentException("Date is null");
+ }
+ ResponseBuilder rb = evaluateIfNotModifiedSince(lastModified);
+ if (rb == null) {
+ rb = evaluateIfModifiedSince(lastModified);
+ }
+ return rb;
+ }
+
+ private ResponseBuilder evaluateIfModifiedSince(Date lastModified) {
+ List<String> ifModifiedSince = headers.getRequestHeader(HttpHeaders.IF_MODIFIED_SINCE);
+
+ if (ifModifiedSince == null || ifModifiedSince.isEmpty()) {
+ return null;
+ }
+
+ SimpleDateFormat dateFormat = HttpUtils.getHttpDateFormat();
+
+ dateFormat.setLenient(false);
+ final Date dateSince;
+ try {
+ dateSince = dateFormat.parse(ifModifiedSince.get(0));
+ } catch (ParseException ex) {
+ // invalid header value, request should continue
+ return Response.status(Response.Status.PRECONDITION_FAILED);
+ }
+
+ if (dateSince.before(lastModified)) {
+ // request should continue
+ return null;
+ }
+
+ return Response.status(Response.Status.NOT_MODIFIED);
+ }
+
+ private ResponseBuilder evaluateIfNotModifiedSince(Date lastModified) {
+ List<String> ifNotModifiedSince = headers.getRequestHeader(HttpHeaders.IF_UNMODIFIED_SINCE);
+
+ if (ifNotModifiedSince == null || ifNotModifiedSince.isEmpty()) {
+ return null;
+ }
+
+ SimpleDateFormat dateFormat = HttpUtils.getHttpDateFormat();
+
+ dateFormat.setLenient(false);
+ final Date dateSince;
+ try {
+ dateSince = dateFormat.parse(ifNotModifiedSince.get(0));
+ } catch (ParseException ex) {
+ // invalid header value, request should continue
+ return Response.status(Response.Status.PRECONDITION_FAILED);
+ }
+
+ if (dateSince.before(lastModified)) {
+ return Response.status(Response.Status.PRECONDITION_FAILED);
+ }
+
+ return null;
+ }
+
+
+
+ public ResponseBuilder evaluatePreconditions(Date lastModified, EntityTag eTag) {
+ if (eTag == null || lastModified == null) {
+ throw new IllegalArgumentException("ETag or Date is null");
+ }
+ return evaluateAll(eTag, lastModified);
+ }
+
+ public String getMethod() {
+ return m.get(Message.HTTP_REQUEST_METHOD).toString();
+ }
+
+
+
+ public ResponseBuilder evaluatePreconditions() {
+ List<String> ifMatch = headers.getRequestHeader(HttpHeaders.IF_MATCH);
+ if (ifMatch != null) {
+ for (String value : ifMatch) {
+ if (!"*".equals(value)) {
+ return Response.status(Response.Status.PRECONDITION_FAILED).tag(EntityTag.valueOf(value));
+ }
+ }
+ }
+ return null;
+ }
+
+ private static class VariantComparator implements Comparator<Variant> {
+
+ static final VariantComparator INSTANCE = new VariantComparator();
+
+ public int compare(Variant v1, Variant v2) {
+ int result = compareMediaTypes(v1.getMediaType(), v2.getMediaType());
+
+ if (result != 0) {
+ return result;
+ }
+
+ result = compareLanguages(v1.getLanguage(), v2.getLanguage());
+
+ if (result == 0) {
+ result = compareEncodings(v1.getEncoding(), v2.getEncoding());
+ }
+
+ return result;
+ }
+
+ private static int compareMediaTypes(MediaType mt1, MediaType mt2) {
+ if (mt1 != null && mt2 == null) {
+ return -1;
+ } else if (mt1 == null && mt2 != null) {
+ return 1;
+ }
+ return JAXRSUtils.compareMediaTypes(mt1, mt2);
+ }
+
+ private static int compareLanguages(Locale l1, Locale l2) {
+ if (l1 != null && l2 == null) {
+ return -1;
+ } else if (l1 == null && l2 != null) {
+ return 1;
+ }
+ return 0;
+ }
+
+ private static int compareEncodings(String enc1, String enc2) {
+ if (enc1 != null && enc2 == null) {
+ return -1;
+ } else if (enc1 == null && enc2 != null) {
+ return 1;
+ }
+ return 0;
+ }
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/impl/ResourceContextImpl.java b/transform/src/patch/java/org/apache/cxf/jaxrs/impl/ResourceContextImpl.java
new file mode 100644
index 0000000..ca1e629
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/impl/ResourceContextImpl.java
@@ -0,0 +1,67 @@
+/**
+ * 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.jaxrs.impl;
+
+import javax.ws.rs.container.ResourceContext;
+
+import org.apache.cxf.jaxrs.ext.ResourceContextProvider;
+import org.apache.cxf.jaxrs.lifecycle.PerRequestResourceProvider;
+import org.apache.cxf.jaxrs.lifecycle.ResourceProvider;
+import org.apache.cxf.jaxrs.model.ClassResourceInfo;
+import org.apache.cxf.jaxrs.model.OperationResourceInfo;
+import org.apache.cxf.jaxrs.provider.ServerProviderFactory;
+import org.apache.cxf.message.Message;
+
+public class ResourceContextImpl implements ResourceContext {
+ private static final String CONTEXT_PROVIDER_PROP = "org.apache.cxf.jaxrs.resource.context.provider";
+ private final ClassResourceInfo cri;
+ private final Class<?> subClass;
+ private final Message m;
+ public ResourceContextImpl(Message m, OperationResourceInfo ori) {
+ this.m = m;
+ this.cri = ori.getClassResourceInfo();
+ this.subClass = ori.getMethodToInvoke().getReturnType();
+ }
+
+ @Override
+ public <T> T getResource(Class<T> cls) {
+ final ResourceProvider rp;
+
+ Object propValue = m.getContextualProperty(CONTEXT_PROVIDER_PROP);
+ if (propValue instanceof ResourceContextProvider) {
+ rp = ((ResourceContextProvider)propValue).getResourceProvider(cls);
+ } else {
+ rp = new PerRequestResourceProvider(cls);
+ }
+ T resource = cls.cast(rp.getInstance(m));
+ return doInitResource(cls, resource);
+ }
+
+ @Override
+ public <T> T initResource(T resource) {
+ return doInitResource(resource.getClass(), resource);
+ }
+
+ private <T> T doInitResource(Class<?> cls, T resource) {
+ ClassResourceInfo sub = cri.getSubResource(subClass, cls, resource, true, m);
+ sub.initBeanParamInfo(ServerProviderFactory.getInstance(m));
+ sub.injectContexts(resource, m.getExchange().get(OperationResourceInfo.class), m);
+ return resource;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/impl/tl/ThreadLocalInvocationHandler.java b/transform/src/patch/java/org/apache/cxf/jaxrs/impl/tl/ThreadLocalInvocationHandler.java
new file mode 100644
index 0000000..bb27b5a
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/impl/tl/ThreadLocalInvocationHandler.java
@@ -0,0 +1,53 @@
+/**
+ * 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.jaxrs.impl.tl;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+
+public class ThreadLocalInvocationHandler<T> extends AbstractThreadLocalProxy<T>
+ implements InvocationHandler {
+
+ public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
+ final Object target;
+ if (m.getDeclaringClass() == ThreadLocalProxy.class) {
+ target = this;
+ } else {
+ target = get();
+ if (target == null) {
+ if (m.getName().endsWith("toString")) {
+ return null;
+ }
+ Class<?> contextCls = m.getDeclaringClass();
+ throw new NullPointerException(
+ contextCls.getName()
+ + " context class has not been injected."
+ + " Check if ContextProvider supporting this class is registered");
+ }
+ }
+ try {
+ return m.invoke(target, args);
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ }
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/interceptor/JAXRSInInterceptor.java b/transform/src/patch/java/org/apache/cxf/jaxrs/interceptor/JAXRSInInterceptor.java
new file mode 100644
index 0000000..3c9c5d8
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/interceptor/JAXRSInInterceptor.java
@@ -0,0 +1,274 @@
+/**
+ * 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.jaxrs.interceptor;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import org.apache.cxf.common.i18n.BundleUtils;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.PropertyUtils;
+import org.apache.cxf.endpoint.Endpoint;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.interceptor.OutgoingChainInterceptor;
+import org.apache.cxf.jaxrs.impl.MetadataMap;
+import org.apache.cxf.jaxrs.impl.RequestPreprocessor;
+import org.apache.cxf.jaxrs.impl.UriInfoImpl;
+import org.apache.cxf.jaxrs.lifecycle.ResourceProvider;
+import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
+import org.apache.cxf.jaxrs.model.ClassResourceInfo;
+import org.apache.cxf.jaxrs.model.OperationResourceInfo;
+import org.apache.cxf.jaxrs.model.URITemplate;
+import org.apache.cxf.jaxrs.provider.ServerProviderFactory;
+import org.apache.cxf.jaxrs.utils.ExceptionUtils;
+import org.apache.cxf.jaxrs.utils.HttpUtils;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageContentsList;
+import org.apache.cxf.phase.AbstractPhaseInterceptor;
+import org.apache.cxf.phase.Phase;
+
+public class JAXRSInInterceptor extends AbstractPhaseInterceptor<Message> {
+
+ private static final Logger LOG = LogUtils.getL7dLogger(JAXRSInInterceptor.class);
+ private static final ResourceBundle BUNDLE = BundleUtils.getBundle(JAXRSInInterceptor.class);
+ private static final String RESOURCE_METHOD = "org.apache.cxf.resource.method";
+ private static final String RESOURCE_OPERATION_NAME = "org.apache.cxf.resource.operation.name";
+
+ public JAXRSInInterceptor() {
+ super(Phase.UNMARSHAL);
+ }
+
+ public void handleMessage(Message message) {
+ final Exchange exchange = message.getExchange();
+
+ exchange.put(Message.REST_MESSAGE, Boolean.TRUE);
+ Response response = exchange.get(Response.class);
+ if (response == null) {
+ try {
+ processRequest(message, exchange);
+ if (exchange.isOneWay()) {
+ ServerProviderFactory.getInstance(message).clearThreadLocalProxies();
+ }
+ } catch (Fault ex) {
+ convertExceptionToResponseIfPossible(ex.getCause(), message);
+ } catch (RuntimeException ex) {
+ convertExceptionToResponseIfPossible(ex, message);
+ } catch (IOException ex) {
+ convertExceptionToResponseIfPossible(ex, message);
+ }
+ }
+
+ response = exchange.get(Response.class);
+ if (response != null) {
+ createOutMessage(message, response);
+ message.getInterceptorChain().doInterceptStartingAt(message,
+ OutgoingChainInterceptor.class.getName());
+ }
+ }
+
+ private void processRequest(Message message, Exchange exchange) throws IOException {
+
+ ServerProviderFactory providerFactory = ServerProviderFactory.getInstance(message);
+
+ RequestPreprocessor rp = providerFactory.getRequestPreprocessor();
+ if (rp != null) {
+ rp.preprocess(message, new UriInfoImpl(message, null));
+ }
+
+ // Global pre-match request filters
+ if (JAXRSUtils.runContainerRequestFilters(providerFactory, message, true, null)) {
+ return;
+ }
+ // HTTP method
+ String httpMethod = HttpUtils.getProtocolHeader(message, Message.HTTP_REQUEST_METHOD,
+ HttpMethod.POST, true);
+
+ // Path to match
+ String rawPath = HttpUtils.getPathToMatch(message, true);
+
+ Map<String, List<String>> protocolHeaders = CastUtils.cast((Map<?, ?>)message.get(Message.PROTOCOL_HEADERS));
+
+ // Content-Type
+ String requestContentType = null;
+ List<String> ctHeaderValues = protocolHeaders.get(Message.CONTENT_TYPE);
+ if (ctHeaderValues != null && !ctHeaderValues.isEmpty()) {
+ requestContentType = ctHeaderValues.get(0);
+ message.put(Message.CONTENT_TYPE, requestContentType);
+ }
+ if (requestContentType == null) {
+ requestContentType = (String)message.get(Message.CONTENT_TYPE);
+
+ if (requestContentType == null) {
+ requestContentType = MediaType.WILDCARD;
+ }
+ }
+
+ // Accept
+ String acceptTypes = null;
+ List<String> acceptHeaderValues = protocolHeaders.get(Message.ACCEPT_CONTENT_TYPE);
+ if (acceptHeaderValues != null && !acceptHeaderValues.isEmpty()) {
+ acceptTypes = acceptHeaderValues.get(0);
+ message.put(Message.ACCEPT_CONTENT_TYPE, acceptTypes);
+ }
+
+ if (acceptTypes == null) {
+ acceptTypes = HttpUtils.getProtocolHeader(message, Message.ACCEPT_CONTENT_TYPE, null);
+ if (acceptTypes == null) {
+ acceptTypes = "*/*";
+ message.put(Message.ACCEPT_CONTENT_TYPE, acceptTypes);
+ }
+ }
+ final List<MediaType> acceptContentTypes;
+ try {
+ acceptContentTypes = JAXRSUtils.sortMediaTypes(acceptTypes, JAXRSUtils.MEDIA_TYPE_Q_PARAM);
+ } catch (IllegalArgumentException ex) {
+ throw ExceptionUtils.toNotAcceptableException(null, null);
+ }
+ exchange.put(Message.ACCEPT_CONTENT_TYPE, acceptContentTypes);
+
+ //1. Matching target resource class
+ List<ClassResourceInfo> resources = JAXRSUtils.getRootResources(message);
+ Map<ClassResourceInfo, MultivaluedMap<String, String>> matchedResources =
+ JAXRSUtils.selectResourceClass(resources, rawPath, message);
+ if (matchedResources == null) {
+ org.apache.cxf.common.i18n.Message errorMsg =
+ new org.apache.cxf.common.i18n.Message("NO_ROOT_EXC",
+ BUNDLE,
+ message.get(Message.REQUEST_URI),
+ rawPath);
+ Level logLevel = JAXRSUtils.getExceptionLogLevel(message, NotFoundException.class);
+ LOG.log(logLevel == null ? Level.FINE : logLevel, errorMsg.toString());
+ Response resp = JAXRSUtils.createResponse(resources, message, errorMsg.toString(),
+ Response.Status.NOT_FOUND.getStatusCode(), false);
+ throw ExceptionUtils.toNotFoundException(null, resp);
+ }
+
+ MultivaluedMap<String, String> matchedValues = new MetadataMap<>();
+
+ final OperationResourceInfo ori;
+
+ try {
+ ori = JAXRSUtils.findTargetMethod(matchedResources, message,
+ httpMethod, matchedValues, requestContentType, acceptContentTypes, true, true);
+ setExchangeProperties(message, exchange, ori, matchedValues, resources.size());
+ } catch (WebApplicationException ex) {
+ if (JAXRSUtils.noResourceMethodForOptions(ex.getResponse(), httpMethod)) {
+ Response response = JAXRSUtils.createResponse(resources, null, null, 200, true);
+ exchange.put(Response.class, response);
+ return;
+ }
+ throw ex;
+ }
+
+
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Request path is: " + rawPath);
+ LOG.fine("Request HTTP method is: " + httpMethod);
+ LOG.fine("Request contentType is: " + requestContentType);
+ LOG.fine("Accept contentType is: " + acceptTypes);
+
+ LOG.fine("Found operation: " + ori.getMethodToInvoke().getName());
+ }
+
+ // Global and name-bound post-match request filters
+ if (!ori.isSubResourceLocator()
+ && JAXRSUtils.runContainerRequestFilters(providerFactory,
+ message,
+ false,
+ ori.getNameBindings())) {
+ return;
+ }
+
+
+ //Process parameters
+ List<Object> params = JAXRSUtils.processParameters(ori, matchedValues, message);
+ message.setContent(List.class, params);
+ }
+
+ private void convertExceptionToResponseIfPossible(Throwable ex, Message message) {
+ Response excResponse = JAXRSUtils.convertFaultToResponse(ex, message);
+ if (excResponse == null) {
+ ServerProviderFactory.getInstance(message).clearThreadLocalProxies();
+ message.getExchange().put(Message.PROPOGATE_EXCEPTION,
+ ExceptionUtils.propogateException(message));
+ throw ex instanceof RuntimeException ? (RuntimeException)ex
+ : ExceptionUtils.toInternalServerErrorException(ex, null);
+ }
+ message.getExchange().put(Response.class, excResponse);
+ message.getExchange().put(Throwable.class, ex);
+ }
+
+ private void setExchangeProperties(Message message,
+ Exchange exchange,
+ OperationResourceInfo ori,
+ MultivaluedMap<String, String> values,
+ int numberOfResources) {
+ final ClassResourceInfo cri = ori.getClassResourceInfo();
+ exchange.put(OperationResourceInfo.class, ori);
+ exchange.put(JAXRSUtils.ROOT_RESOURCE_CLASS, cri);
+ message.put(RESOURCE_METHOD, ori.getMethodToInvoke());
+ message.put(URITemplate.TEMPLATE_PARAMETERS, values);
+ message.put(URITemplate.URI_TEMPLATE, JAXRSUtils.getUriTemplate(message, cri, ori));
+
+ String plainOperationName = ori.getMethodToInvoke().getName();
+ if (numberOfResources > 1) {
+ plainOperationName = cri.getServiceClass().getSimpleName() + "#" + plainOperationName;
+ }
+ exchange.put(RESOURCE_OPERATION_NAME, plainOperationName);
+
+ if (ori.isOneway()
+ || PropertyUtils.isTrue(HttpUtils.getProtocolHeader(message, Message.ONE_WAY_REQUEST, null))) {
+ exchange.setOneWay(true);
+ }
+ ResourceProvider rp = cri.getResourceProvider();
+ if (rp instanceof SingletonResourceProvider) {
+ //cri.isSingleton is not guaranteed to indicate we have a 'pure' singleton
+ exchange.put(Message.SERVICE_OBJECT, rp.getInstance(message));
+ }
+ }
+
+ private Message createOutMessage(Message inMessage, Response r) {
+ Endpoint e = inMessage.getExchange().getEndpoint();
+ Message mout = e.getBinding().createMessage();
+ mout.setContent(List.class, new MessageContentsList(r));
+ mout.setExchange(inMessage.getExchange());
+ mout.setInterceptorChain(
+ OutgoingChainInterceptor.getOutInterceptorChain(inMessage.getExchange()));
+ inMessage.getExchange().setOutMessage(mout);
+ if (r.getStatus() >= Response.Status.BAD_REQUEST.getStatusCode()) {
+ inMessage.getExchange().put("cxf.io.cacheinput", Boolean.FALSE);
+ }
+ return mout;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/interceptor/JAXRSOutInterceptor.java b/transform/src/patch/java/org/apache/cxf/jaxrs/interceptor/JAXRSOutInterceptor.java
new file mode 100644
index 0000000..ad75c0f
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/interceptor/JAXRSOutInterceptor.java
@@ -0,0 +1,498 @@
+/**
+ * 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.jaxrs.interceptor;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.WriterInterceptor;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.stream.events.XMLEvent;
+
+import org.apache.cxf.common.i18n.BundleUtils;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.PropertyUtils;
+import org.apache.cxf.interceptor.AbstractOutDatabindingInterceptor;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.io.CachedOutputStream;
+import org.apache.cxf.jaxrs.impl.ResponseImpl;
+import org.apache.cxf.jaxrs.impl.WriterInterceptorMBW;
+import org.apache.cxf.jaxrs.model.OperationResourceInfo;
+import org.apache.cxf.jaxrs.provider.AbstractConfigurableProvider;
+import org.apache.cxf.jaxrs.provider.ServerProviderFactory;
+import org.apache.cxf.jaxrs.utils.AnnotationUtils;
+import org.apache.cxf.jaxrs.utils.ExceptionUtils;
+import org.apache.cxf.jaxrs.utils.HttpUtils;
+import org.apache.cxf.jaxrs.utils.InjectionUtils;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageContentsList;
+import org.apache.cxf.message.MessageUtils;
+import org.apache.cxf.phase.Phase;
+import org.apache.cxf.staxutils.CachingXmlEventWriter;
+import org.apache.cxf.staxutils.StaxUtils;
+import org.apache.cxf.transport.http.AbstractHTTPDestination;
+
+public class JAXRSOutInterceptor extends AbstractOutDatabindingInterceptor {
+ private static final Logger LOG = LogUtils.getL7dLogger(JAXRSOutInterceptor.class);
+ private static final ResourceBundle BUNDLE = BundleUtils.getBundle(JAXRSOutInterceptor.class);
+
+ public JAXRSOutInterceptor() {
+ super(Phase.MARSHAL);
+ }
+
+ public void handleMessage(Message message) {
+ ServerProviderFactory providerFactory = ServerProviderFactory.getInstance(message);
+ try {
+ processResponse(providerFactory, message);
+ } finally {
+ ServerProviderFactory.releaseRequestState(providerFactory, message);
+ }
+
+
+ }
+
+ @SuppressWarnings("resource") // Response shouldn't be closed here
+ private void processResponse(ServerProviderFactory providerFactory, Message message) {
+
+ if (isResponseAlreadyHandled(message)) {
+ return;
+ }
+ MessageContentsList objs = MessageContentsList.getContentsList(message);
+ if (objs == null || objs.isEmpty()) {
+ return;
+ }
+
+ Object responseObj = objs.get(0);
+
+ final Response response;
+ if (responseObj instanceof Response) {
+ response = (Response)responseObj;
+ if (response.getStatus() == 500
+ && message.getExchange().get(JAXRSUtils.EXCEPTION_FROM_MAPPER) != null) {
+ message.put(Message.RESPONSE_CODE, 500);
+ return;
+ }
+ } else {
+ int status = getStatus(message, responseObj != null ? 200 : 204);
+ response = JAXRSUtils.toResponseBuilder(status).entity(responseObj).build();
+ }
+
+ Exchange exchange = message.getExchange();
+ OperationResourceInfo ori = (OperationResourceInfo)exchange.get(OperationResourceInfo.class
+ .getName());
+
+ serializeMessage(providerFactory, message, response, ori, true);
+ }
+
+
+
+ private int getStatus(Message message, int defaultValue) {
+ Object customStatus = message.getExchange().get(Message.RESPONSE_CODE);
+ return customStatus == null ? defaultValue : (Integer)customStatus;
+ }
+
+ private void serializeMessage(ServerProviderFactory providerFactory,
+ Message message,
+ Response theResponse,
+ OperationResourceInfo ori,
+ boolean firstTry) {
+
+ ResponseImpl response = (ResponseImpl)JAXRSUtils.copyResponseIfNeeded(theResponse);
+
+ final Exchange exchange = message.getExchange();
+
+ boolean headResponse = response.getStatus() == 200 && firstTry
+ && ori != null && HttpMethod.HEAD.equals(ori.getHttpMethod());
+ Object entity = response.getActualEntity();
+ if (headResponse && entity != null) {
+ LOG.info(new org.apache.cxf.common.i18n.Message("HEAD_WITHOUT_ENTITY", BUNDLE).toString());
+ entity = null;
+ }
+
+
+ Method invoked = ori == null ? null : ori.getAnnotatedMethod() != null
+ ? ori.getAnnotatedMethod() : ori.getMethodToInvoke();
+
+ Annotation[] annotations;
+ Annotation[] staticAnns = ori != null ? ori.getOutAnnotations() : new Annotation[]{};
+ Annotation[] responseAnns = response.getEntityAnnotations();
+ if (responseAnns != null) {
+ annotations = new Annotation[staticAnns.length + responseAnns.length];
+ System.arraycopy(staticAnns, 0, annotations, 0, staticAnns.length);
+ System.arraycopy(responseAnns, 0, annotations, staticAnns.length, responseAnns.length);
+ } else {
+ annotations = staticAnns;
+ }
+
+ response.setStatus(getActualStatus(response.getStatus(), entity));
+ response.setEntity(entity, annotations);
+
+ // Prepare the headers
+ MultivaluedMap<String, Object> responseHeaders =
+ prepareResponseHeaders(message, response, entity, firstTry);
+
+ // Run the filters
+ try {
+ JAXRSUtils.runContainerResponseFilters(providerFactory, response, message, ori, invoked);
+ } catch (Throwable ex) {
+ handleWriteException(providerFactory, message, ex, firstTry);
+ return;
+ }
+
+ // Write the entity
+ entity = InjectionUtils.getEntity(response.getActualEntity());
+ setResponseStatus(message, getActualStatus(response.getStatus(), entity));
+ if (entity == null) {
+ if (!headResponse) {
+ responseHeaders.putSingle(HttpHeaders.CONTENT_LENGTH, "0");
+ if (MessageUtils.getContextualBoolean(message, "remove.content.type.for.empty.response", false)) {
+ responseHeaders.remove(HttpHeaders.CONTENT_TYPE);
+ message.remove(Message.CONTENT_TYPE);
+ }
+ }
+ HttpUtils.convertHeaderValuesToString(responseHeaders, true);
+ return;
+ }
+
+ Object ignoreWritersProp = exchange.get(JAXRSUtils.IGNORE_MESSAGE_WRITERS);
+ boolean ignoreWriters =
+ ignoreWritersProp != null && Boolean.valueOf(ignoreWritersProp.toString());
+ if (ignoreWriters) {
+ writeResponseToStream(message.getContent(OutputStream.class), entity);
+ return;
+ }
+
+ MediaType responseMediaType =
+ getResponseMediaType(responseHeaders.getFirst(HttpHeaders.CONTENT_TYPE));
+
+ Class<?> serviceCls = invoked != null ? ori.getClassResourceInfo().getServiceClass() : null;
+ Class<?> targetType = InjectionUtils.getRawResponseClass(entity);
+ Type genericType = InjectionUtils.getGenericResponseType(invoked, serviceCls,
+ response.getActualEntity(), targetType, exchange);
+ targetType = InjectionUtils.updateParamClassToTypeIfNeeded(targetType, genericType);
+ annotations = response.getEntityAnnotations();
+
+ List<WriterInterceptor> writers = providerFactory
+ .createMessageBodyWriterInterceptor(targetType, genericType, annotations, responseMediaType, message,
+ ori == null ? null : ori.getNameBindings());
+
+ OutputStream outOriginal = message.getContent(OutputStream.class);
+ if (writers == null || writers.isEmpty()) {
+ writeResponseErrorMessage(message, outOriginal, "NO_MSG_WRITER", targetType, responseMediaType);
+ return;
+ }
+ try {
+ boolean checkWriters = false;
+ if (responseMediaType.isWildcardSubtype()) {
+ Produces pM = AnnotationUtils.getMethodAnnotation(ori == null ? null : ori.getAnnotatedMethod(),
+ Produces.class);
+ Produces pC = AnnotationUtils.getClassAnnotation(serviceCls, Produces.class);
+ checkWriters = pM == null && pC == null;
+ }
+ responseMediaType = checkFinalContentType(responseMediaType, writers, checkWriters);
+ } catch (Throwable ex) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.log(Level.FINE, ex.getMessage(), ex);
+ }
+ handleWriteException(providerFactory, message, ex, firstTry);
+ return;
+ }
+ String finalResponseContentType = JAXRSUtils.mediaTypeToString(responseMediaType);
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Response content type is: " + finalResponseContentType);
+ }
+ responseHeaders.putSingle(HttpHeaders.CONTENT_TYPE, finalResponseContentType);
+ message.put(Message.CONTENT_TYPE, finalResponseContentType);
+
+ boolean enabled = checkBufferingMode(message, writers, firstTry);
+ try {
+
+ try { // NOPMD
+ JAXRSUtils.writeMessageBody(writers,
+ entity,
+ targetType,
+ genericType,
+ annotations,
+ responseMediaType,
+ responseHeaders,
+ message);
+
+ if (isResponseRedirected(message)) {
+ return;
+ }
+ checkCachedStream(message, outOriginal, enabled);
+ } finally {
+ if (enabled) {
+ OutputStream os = message.getContent(OutputStream.class);
+ if (os != outOriginal && os instanceof CachedOutputStream) {
+ os.close();
+ }
+ message.setContent(OutputStream.class, outOriginal);
+ message.put(XMLStreamWriter.class.getName(), null);
+ }
+ }
+
+ } catch (Throwable ex) {
+ logWriteError(firstTry, targetType, responseMediaType);
+ handleWriteException(providerFactory, message, ex, firstTry);
+ }
+ }
+
+ private MultivaluedMap<String, Object> prepareResponseHeaders(Message message,
+ ResponseImpl response,
+ Object entity,
+ boolean firstTry) {
+ MultivaluedMap<String, Object> responseHeaders = response.getMetadata();
+ @SuppressWarnings("unchecked")
+ Map<String, List<Object>> userHeaders = (Map<String, List<Object>>)message.get(Message.PROTOCOL_HEADERS);
+ if (firstTry && userHeaders != null) {
+ responseHeaders.putAll(userHeaders);
+ }
+ if (entity != null) {
+ Object customContentType = responseHeaders.getFirst(HttpHeaders.CONTENT_TYPE);
+ if (customContentType == null) {
+ String initialResponseContentType = (String)message.get(Message.CONTENT_TYPE);
+ if (initialResponseContentType != null) {
+ responseHeaders.putSingle(HttpHeaders.CONTENT_TYPE, initialResponseContentType);
+ }
+ } else {
+ message.put(Message.CONTENT_TYPE, customContentType.toString());
+ }
+ }
+ message.put(Message.PROTOCOL_HEADERS, responseHeaders);
+ setResponseDate(responseHeaders, firstTry);
+ return responseHeaders;
+ }
+
+ private MediaType getResponseMediaType(Object mediaTypeHeader) {
+ MediaType responseMediaType;
+ if (mediaTypeHeader instanceof MediaType) {
+ responseMediaType = (MediaType)mediaTypeHeader;
+ } else {
+ responseMediaType = mediaTypeHeader == null ? MediaType.WILDCARD_TYPE
+ : JAXRSUtils.toMediaType(mediaTypeHeader.toString());
+ }
+ return responseMediaType;
+ }
+
+ private int getActualStatus(int status, Object responseObj) {
+ if (status == -1) {
+ return responseObj == null ? 204 : 200;
+ }
+ return status;
+ }
+
+ private boolean checkBufferingMode(Message m, List<WriterInterceptor> writers, boolean firstTry) {
+ if (!firstTry) {
+ return false;
+ }
+ WriterInterceptor last = writers.get(writers.size() - 1);
+ MessageBodyWriter<Object> w = ((WriterInterceptorMBW)last).getMBW();
+ Object outBuf = m.getContextualProperty(OUT_BUFFERING);
+ boolean enabled = PropertyUtils.isTrue(outBuf);
+ boolean configurableProvider = w instanceof AbstractConfigurableProvider;
+ if (!enabled && outBuf == null && configurableProvider) {
+ enabled = ((AbstractConfigurableProvider)w).getEnableBuffering();
+ }
+ if (enabled) {
+ boolean streamingOn = configurableProvider
+ && ((AbstractConfigurableProvider)w).getEnableStreaming();
+ if (streamingOn) {
+ m.setContent(XMLStreamWriter.class, new CachingXmlEventWriter());
+ } else {
+ m.setContent(OutputStream.class, new CachedOutputStream());
+ }
+ }
+ return enabled;
+ }
+
+ private void checkCachedStream(Message m, OutputStream osOriginal, boolean enabled) throws Exception {
+ final XMLStreamWriter writer;
+ if (enabled) {
+ writer = m.getContent(XMLStreamWriter.class);
+ } else {
+ writer = (XMLStreamWriter)m.get(XMLStreamWriter.class.getName());
+ }
+ if (writer instanceof CachingXmlEventWriter) {
+ CachingXmlEventWriter cache = (CachingXmlEventWriter)writer;
+ if (!cache.getEvents().isEmpty()) {
+ XMLStreamWriter origWriter = null;
+ try {
+ origWriter = StaxUtils.createXMLStreamWriter(osOriginal);
+ for (XMLEvent event : cache.getEvents()) {
+ StaxUtils.writeEvent(event, origWriter);
+ }
+ } finally {
+ StaxUtils.close(origWriter);
+ }
+ }
+ m.setContent(XMLStreamWriter.class, null);
+ return;
+ }
+ if (enabled) {
+ OutputStream os = m.getContent(OutputStream.class);
+ if (os != osOriginal && os instanceof CachedOutputStream) {
+ CachedOutputStream cos = (CachedOutputStream)os;
+ if (cos.size() != 0) {
+ cos.writeCacheTo(osOriginal);
+ }
+ }
+ }
+ }
+
+ private void logWriteError(boolean firstTry, Class<?> cls, MediaType ct) {
+ if (firstTry) {
+ JAXRSUtils.logMessageHandlerProblem("MSG_WRITER_PROBLEM", cls, ct);
+ }
+ }
+
+ private void handleWriteException(ServerProviderFactory pf,
+ Message message,
+ Throwable ex,
+ boolean firstTry) {
+ Response excResponse = null;
+ if (firstTry) {
+ excResponse = JAXRSUtils.convertFaultToResponse(ex, message);
+ } else {
+ message.getExchange().put(JAXRSUtils.SECOND_JAXRS_EXCEPTION, Boolean.TRUE);
+ }
+ if (excResponse == null) {
+ setResponseStatus(message, 500);
+ throw new Fault(ex);
+ }
+ serializeMessage(pf, message, excResponse, null, false);
+
+ }
+
+
+ private void writeResponseErrorMessage(Message message, OutputStream out,
+ String name, Class<?> cls, MediaType ct) {
+ message.put(Message.CONTENT_TYPE, "text/plain");
+ message.put(Message.RESPONSE_CODE, 500);
+ try {
+ String errorMessage = JAXRSUtils.logMessageHandlerProblem(name, cls, ct);
+ if (out != null) {
+ out.write(errorMessage.getBytes(StandardCharsets.UTF_8));
+ }
+ } catch (IOException another) {
+ // ignore
+ }
+ }
+
+
+ private MediaType checkFinalContentType(MediaType mt, List<WriterInterceptor> writers, boolean checkWriters) {
+ if (checkWriters) {
+ int mbwIndex = writers.size() == 1 ? 0 : writers.size() - 1;
+ MessageBodyWriter<Object> writer = ((WriterInterceptorMBW)writers.get(mbwIndex)).getMBW();
+ Produces pm = writer.getClass().getAnnotation(Produces.class);
+ if (pm != null) {
+ List<MediaType> sorted =
+ JAXRSUtils.sortMediaTypes(JAXRSUtils.getMediaTypes(pm.value()), JAXRSUtils.MEDIA_TYPE_QS_PARAM);
+ mt = JAXRSUtils.intersectMimeTypes(sorted, mt).get(0);
+ }
+ }
+ if (mt.isWildcardType() || mt.isWildcardSubtype()) {
+ if ("application".equals(mt.getType()) || mt.isWildcardType()) {
+ mt = MediaType.APPLICATION_OCTET_STREAM_TYPE;
+ } else {
+ throw ExceptionUtils.toNotAcceptableException(null, null);
+ }
+ }
+ return mt;
+ }
+
+ private void setResponseDate(MultivaluedMap<String, Object> headers, boolean firstTry) {
+ if (!firstTry || headers.containsKey(HttpHeaders.DATE)) {
+ return;
+ }
+ SimpleDateFormat format = HttpUtils.getHttpDateFormat();
+ headers.putSingle(HttpHeaders.DATE, format.format(new Date()));
+ }
+
+ private boolean isResponseAlreadyHandled(Message m) {
+ return isResponseAlreadyCommited(m) || isResponseRedirected(m);
+ }
+
+ private boolean isResponseAlreadyCommited(Message m) {
+ return Boolean.TRUE.equals(m.getExchange().get(AbstractHTTPDestination.RESPONSE_COMMITED));
+ }
+
+ private boolean isResponseRedirected(Message m) {
+ return Boolean.TRUE.equals(m.getExchange().get(AbstractHTTPDestination.REQUEST_REDIRECTED));
+ }
+
+ private void writeResponseToStream(OutputStream os, Object responseObj) {
+ try {
+ byte[] bytes = responseObj.toString().getBytes(StandardCharsets.UTF_8);
+ os.write(bytes, 0, bytes.length);
+ } catch (Exception ex) {
+ LOG.severe("Problem with writing the data to the output stream");
+ ex.printStackTrace();
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private void setResponseStatus(Message message, int status) {
+ message.put(Message.RESPONSE_CODE, status);
+ boolean responseHeadersCopied = isResponseHeadersCopied(message);
+ if (responseHeadersCopied) {
+ HttpServletResponse response =
+ (HttpServletResponse)message.get(AbstractHTTPDestination.HTTP_RESPONSE);
+ response.setStatus(status);
+ }
+ }
+
+ // Some CXF interceptors such as FIStaxOutInterceptor will indirectly initiate
+ // an early copying of response code and headers into the HttpServletResponse
+ // TODO : Pushing the filter processing and copying response headers into say
+ // PRE-LOGICAl and PREPARE_SEND interceptors will most likely be a good thing
+ // however JAX-RS MessageBodyWriters are also allowed to add response headers
+ // which is reason why a MultipartMap parameter in MessageBodyWriter.writeTo
+ // method is modifiable. Thus we do need to know if the initial copy has already
+ // occurred: for now we will just use to ensure the correct status is set
+ private boolean isResponseHeadersCopied(Message message) {
+ return PropertyUtils.isTrue(message.get(AbstractHTTPDestination.RESPONSE_HEADERS_COPIED));
+ }
+
+ public void handleFault(Message message) {
+ // complete
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/model/AbstractResourceInfo.java b/transform/src/patch/java/org/apache/cxf/jaxrs/model/AbstractResourceInfo.java
new file mode 100644
index 0000000..edf010e
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/model/AbstractResourceInfo.java
@@ -0,0 +1,389 @@
+/**
+ * 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.jaxrs.model;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Context;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.ReflectionUtil;
+import org.apache.cxf.jaxrs.impl.tl.ThreadLocalProxy;
+import org.apache.cxf.jaxrs.utils.InjectionUtils;
+
+public abstract class AbstractResourceInfo {
+ public static final String CONSTRUCTOR_PROXY_MAP = "jaxrs-constructor-proxy-map";
+ private static final Logger LOG = LogUtils.getL7dLogger(AbstractResourceInfo.class);
+ private static final String FIELD_PROXY_MAP = "jaxrs-field-proxy-map";
+ private static final String SETTER_PROXY_MAP = "jaxrs-setter-proxy-map";
+
+ protected boolean root;
+ protected Class<?> resourceClass;
+ protected Class<?> serviceClass;
+
+ private Map<Class<?>, List<Field>> contextFields;
+ private Map<Class<?>, Map<Class<?>, Method>> contextMethods;
+ private Bus bus;
+ private boolean constructorProxiesAvailable;
+ private boolean contextsAvailable;
+
+ protected AbstractResourceInfo(Bus bus) {
+ this.bus = bus;
+ }
+
+ protected AbstractResourceInfo(Class<?> resourceClass, Class<?> serviceClass,
+ boolean isRoot, boolean checkContexts, Bus bus) {
+ this(resourceClass, serviceClass, isRoot, checkContexts, null, bus, null);
+ }
+
+ protected AbstractResourceInfo(Class<?> resourceClass,
+ Class<?> serviceClass,
+ boolean isRoot,
+ boolean checkContexts,
+ Map<Class<?>, ThreadLocalProxy<?>> constructorProxies,
+ Bus bus,
+ Object provider) {
+ this.bus = bus;
+ this.serviceClass = serviceClass;
+ this.resourceClass = resourceClass;
+ root = isRoot;
+ if (checkContexts && resourceClass != null) {
+ findContexts(serviceClass, provider, constructorProxies);
+ }
+ }
+
+ private void findContexts(Class<?> cls, Object provider,
+ Map<Class<?>, ThreadLocalProxy<?>> constructorProxies) {
+ findContextFields(cls, provider);
+ findContextSetterMethods(cls, provider);
+ if (constructorProxies != null) {
+ Map<Class<?>, Map<Class<?>, ThreadLocalProxy<?>>> proxies = getConstructorProxyMap();
+ proxies.put(serviceClass, constructorProxies);
+ constructorProxiesAvailable = true;
+ }
+
+
+ contextsAvailable = contextFields != null && !contextFields.isEmpty()
+ || contextMethods != null && !contextMethods.isEmpty()
+ || constructorProxiesAvailable;
+ }
+
+ public boolean contextsAvailable() {
+ return contextsAvailable;
+ }
+
+ public Bus getBus() {
+ return bus;
+ }
+
+ public void setResourceClass(Class<?> rClass) {
+ resourceClass = rClass;
+ if (serviceClass.isInterface() && resourceClass != null && !resourceClass.isInterface()) {
+ findContexts(resourceClass, null, null);
+ }
+ }
+
+ public Class<?> getServiceClass() {
+ return serviceClass;
+ }
+
+ private void findContextFields(Class<?> cls, Object provider) {
+ if (cls == Object.class || cls == null) {
+ return;
+ }
+ for (Field f : ReflectionUtil.getDeclaredFields(cls)) {
+ for (Annotation a : f.getAnnotations()) {
+ if (a.annotationType() == Context.class
+ && (f.getType().isInterface() || f.getType() == Application.class)) {
+ contextFields = addContextField(contextFields, f);
+ checkContextClass(f.getType());
+ if (!InjectionUtils.VALUE_CONTEXTS.contains(f.getType().getName())) {
+ addToMap(getFieldProxyMap(true), f, getFieldThreadLocalProxy(f, provider));
+ }
+ }
+ }
+ }
+ findContextFields(cls.getSuperclass(), provider);
+ }
+
+ private static ThreadLocalProxy<?> getFieldThreadLocalProxy(Field f, Object provider) {
+ if (provider != null) {
+ Object proxy = null;
+ synchronized (provider) {
+ try {
+ proxy = InjectionUtils.extractFieldValue(f, provider);
+ } catch (Throwable t) {
+ // continue
+ }
+ if (!(proxy instanceof ThreadLocalProxy)) {
+ proxy = InjectionUtils.createThreadLocalProxy(f.getType());
+ InjectionUtils.injectFieldValue(f, provider, proxy);
+ }
+ }
+ return (ThreadLocalProxy<?>)proxy;
+ }
+ return InjectionUtils.createThreadLocalProxy(f.getType());
+ }
+
+ private static ThreadLocalProxy<?> getMethodThreadLocalProxy(Method m, Object provider) {
+ if (provider != null) {
+ Object proxy = null;
+ synchronized (provider) {
+ try {
+ proxy = InjectionUtils.extractFromMethod(provider,
+ InjectionUtils.getGetterFromSetter(m),
+ false);
+ } catch (Throwable t) {
+ // continue
+ }
+ if (!(proxy instanceof ThreadLocalProxy)) {
+ proxy = InjectionUtils.createThreadLocalProxy(m.getParameterTypes()[0]);
+ InjectionUtils.injectThroughMethod(provider, m, proxy);
+ }
+ }
+ return (ThreadLocalProxy<?>)proxy;
+ }
+ return InjectionUtils.createThreadLocalProxy(m.getParameterTypes()[0]);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T> Map<Class<?>, Map<T, ThreadLocalProxy<?>>> getProxyMap(String prop, boolean create) {
+ // Avoid synchronizing on the bus for a ConcurrentHashMAp
+ if (bus.getProperties() instanceof ConcurrentHashMap) {
+ return (Map<Class<?>, Map<T, ThreadLocalProxy<?>>>) bus.getProperties().computeIfAbsent(prop, k ->
+ new ConcurrentHashMap<Class<?>, Map<T, ThreadLocalProxy<?>>>(2)
+ );
+ }
+
+ Object property;
+ synchronized (bus) {
+ property = bus.getProperty(prop);
+ if (property == null && create) {
+ Map<Class<?>, Map<T, ThreadLocalProxy<?>>> map
+ = new ConcurrentHashMap<>(2);
+ bus.setProperty(prop, map);
+ property = map;
+ }
+ }
+ return (Map<Class<?>, Map<T, ThreadLocalProxy<?>>>)property;
+ }
+
+ public Map<Class<?>, ThreadLocalProxy<?>> getConstructorProxies() {
+ if (constructorProxiesAvailable) {
+ return getConstructorProxyMap().get(serviceClass);
+ }
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map<Class<?>, Map<Class<?>, ThreadLocalProxy<?>>> getConstructorProxyMap() {
+ Object property = bus.getProperty(CONSTRUCTOR_PROXY_MAP);
+ if (property == null) {
+ Map<Class<?>, Map<Class<?>, ThreadLocalProxy<?>>> map
+ = new ConcurrentHashMap<>(2);
+ bus.setProperty(CONSTRUCTOR_PROXY_MAP, map);
+ property = map;
+ }
+ return (Map<Class<?>, Map<Class<?>, ThreadLocalProxy<?>>>)property;
+ }
+
+ private Map<Class<?>, Map<Field, ThreadLocalProxy<?>>> getFieldProxyMap(boolean create) {
+ return getProxyMap(FIELD_PROXY_MAP, create);
+ }
+
+ private Map<Class<?>, Map<Method, ThreadLocalProxy<?>>> getSetterProxyMap(boolean create) {
+ return getProxyMap(SETTER_PROXY_MAP, create);
+ }
+
+ private void findContextSetterMethods(Class<?> cls, Object provider) {
+
+ for (Method m : cls.getMethods()) {
+
+ if (!m.getName().startsWith("set") || m.getParameterTypes().length != 1) {
+ continue;
+ }
+ for (Annotation a : m.getAnnotations()) {
+ if (a.annotationType() == Context.class) {
+ checkContextMethod(m, provider);
+ break;
+ }
+ }
+ }
+ Class<?>[] interfaces = cls.getInterfaces();
+ for (Class<?> i : interfaces) {
+ findContextSetterMethods(i, provider);
+ }
+ Class<?> superCls = cls.getSuperclass();
+ if (superCls != null && superCls != Object.class) {
+ findContextSetterMethods(superCls, provider);
+ }
+ }
+
+ private void checkContextMethod(Method m, Object provider) {
+ Class<?> type = m.getParameterTypes()[0];
+ if (type.isInterface() || type == Application.class) {
+ checkContextClass(type);
+ addContextMethod(type, m, provider);
+ }
+ }
+ private void checkContextClass(Class<?> type) {
+ if (!InjectionUtils.STANDARD_CONTEXT_CLASSES.contains(type.getName())) {
+ LOG.fine("Injecting a custom context " + type.getName()
+ + ", ContextProvider is required for this type");
+ }
+ }
+
+ public Map<Class<?>, Method> getContextMethods() {
+ Map<Class<?>, Method> methods = contextMethods == null ? null : contextMethods.get(getServiceClass());
+ return methods == null ? Collections.emptyMap()
+ : Collections.unmodifiableMap(methods);
+ }
+
+ private void addContextMethod(Class<?> contextClass, Method m, Object provider) {
+ if (contextMethods == null) {
+ contextMethods = new HashMap<>();
+ }
+ addToMap(contextMethods, contextClass, m);
+ if (!InjectionUtils.VALUE_CONTEXTS.contains(m.getParameterTypes()[0].getName())) {
+ addToMap(getSetterProxyMap(true), m, getMethodThreadLocalProxy(m, provider));
+ }
+ }
+
+ public boolean isRoot() {
+ return root;
+ }
+
+ public Class<?> getResourceClass() {
+ return resourceClass;
+ }
+
+ public List<Field> getContextFields() {
+ return getList(contextFields);
+ }
+
+ public ThreadLocalProxy<?> getContextFieldProxy(Field f) {
+ return getProxy(getFieldProxyMap(true), f);
+ }
+
+ public ThreadLocalProxy<?> getContextSetterProxy(Method m) {
+ return getProxy(getSetterProxyMap(true), m);
+ }
+
+ public abstract boolean isSingleton();
+
+ @SuppressWarnings("rawtypes")
+ public static void clearAllMaps() {
+ Bus bus = BusFactory.getThreadDefaultBus(false);
+ if (bus != null) {
+ Object property = bus.getProperty(FIELD_PROXY_MAP);
+ if (property != null) {
+ ((Map)property).clear();
+ }
+ property = bus.getProperty(SETTER_PROXY_MAP);
+ if (property != null) {
+ ((Map)property).clear();
+ }
+ property = bus.getProperty(CONSTRUCTOR_PROXY_MAP);
+ if (property != null) {
+ ((Map)property).clear();
+ }
+ }
+ }
+
+ public void clearThreadLocalProxies() {
+ clearProxies(getFieldProxyMap(false));
+ clearProxies(getSetterProxyMap(false));
+ clearProxies(getConstructorProxyMap());
+ }
+
+ private <T> void clearProxies(Map<Class<?>, Map<T, ThreadLocalProxy<?>>> tlps) {
+ Map<T, ThreadLocalProxy<?>> proxies = tlps == null ? null : tlps.get(getServiceClass());
+ if (proxies == null) {
+ return;
+ }
+ for (ThreadLocalProxy<?> tlp : proxies.values()) {
+ if (tlp != null) {
+ tlp.remove();
+ }
+ }
+ }
+
+ private Map<Class<?>, List<Field>> addContextField(Map<Class<?>, List<Field>> theFields, Field f) {
+ if (theFields == null) {
+ theFields = new HashMap<>();
+ }
+
+ List<Field> fields = theFields.get(serviceClass);
+ if (fields == null) {
+ fields = new ArrayList<>();
+ theFields.put(serviceClass, fields);
+ }
+ if (!fields.contains(f)) {
+ fields.add(f);
+ }
+ return theFields;
+ }
+
+ private <T, V> void addToMap(Map<Class<?>, Map<T, V>> proxyMap,
+ T f,
+ V proxy) {
+ Map<T, V> proxies = proxyMap.get(serviceClass);
+ if (proxies == null) {
+ proxies = new ConcurrentHashMap<>();
+ proxyMap.put(serviceClass, proxies);
+ }
+ if (!proxies.containsKey(f)) {
+ proxies.put(f, proxy);
+ }
+ }
+
+ private List<Field> getList(Map<Class<?>, List<Field>> fields) {
+ List<Field> ret = fields == null ? null : fields.get(getServiceClass());
+ if (ret != null) {
+ ret = Collections.unmodifiableList(ret);
+ } else {
+ ret = Collections.emptyList();
+ }
+ return ret;
+ }
+
+ private <T> ThreadLocalProxy<?> getProxy(Map<Class<?>, Map<T, ThreadLocalProxy<?>>> proxies,
+ T key) {
+
+ Map<?, ThreadLocalProxy<?>> theMap = proxies == null ? null : proxies.get(getServiceClass());
+ ThreadLocalProxy<?> ret = null;
+ if (theMap != null) {
+ ret = theMap.get(key);
+ }
+ return ret;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/model/ClassResourceInfo.java b/transform/src/patch/java/org/apache/cxf/jaxrs/model/ClassResourceInfo.java
new file mode 100644
index 0000000..a69cac1
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/model/ClassResourceInfo.java
@@ -0,0 +1,366 @@
+/**
+ * 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.jaxrs.model;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.common.util.ClassHelper;
+import org.apache.cxf.jaxrs.lifecycle.ResourceProvider;
+import org.apache.cxf.jaxrs.provider.ServerProviderFactory;
+import org.apache.cxf.jaxrs.utils.AnnotationUtils;
+import org.apache.cxf.jaxrs.utils.InjectionUtils;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.jaxrs.utils.ResourceUtils;
+import org.apache.cxf.message.Message;
+
+public class ClassResourceInfo extends BeanResourceInfo {
+
+ private URITemplate uriTemplate;
+ private MethodDispatcher methodDispatcher;
+ private ResourceProvider resourceProvider;
+ private ConcurrentHashMap<SubresourceKey, ClassResourceInfo> subResources
+ = new ConcurrentHashMap<>();
+
+ private boolean enableStatic;
+ private boolean createdFromModel;
+ private String consumesTypes;
+ private String producesTypes;
+ private List<MediaType> defaultConsumes = Collections.singletonList(JAXRSUtils.ALL_TYPES);
+ private List<MediaType> defaultProduces = Collections.singletonList(JAXRSUtils.ALL_TYPES);
+ private Set<String> nameBindings = Collections.emptySet();
+ private ClassResourceInfo parent;
+ private Set<String> injectedSubInstances = new HashSet<>();
+ public ClassResourceInfo(ClassResourceInfo cri) {
+ super(cri.getBus());
+ if (cri.isCreatedFromModel() && !InjectionUtils.isConcreteClass(cri.getServiceClass())) {
+ this.root = cri.root;
+ this.serviceClass = cri.serviceClass;
+ this.uriTemplate = cri.uriTemplate;
+ this.methodDispatcher = new MethodDispatcher(cri.methodDispatcher, this);
+ this.subResources = cri.subResources;
+ //CHECKSTYLE:OFF
+ this.paramFields = cri.paramFields;
+ this.paramMethods = cri.paramMethods;
+ //CHECKSTYLE:ON
+ this.enableStatic = true;
+ this.nameBindings = cri.nameBindings;
+ this.parent = cri.parent;
+ } else {
+ throw new IllegalArgumentException();
+ }
+
+ }
+
+ public ClassResourceInfo(Class<?> theResourceClass, Class<?> theServiceClass,
+ boolean theRoot, boolean enableStatic, Bus bus) {
+ super(theResourceClass, theServiceClass, theRoot, theRoot || enableStatic, bus);
+ this.enableStatic = enableStatic;
+ if (resourceClass != null) {
+ nameBindings = AnnotationUtils.getNameBindings(bus, serviceClass);
+ }
+ }
+ //CHECKSTYLE:OFF
+ public ClassResourceInfo(Class<?> theResourceClass, Class<?> theServiceClass,
+ boolean theRoot, boolean enableStatic, Bus bus, List<MediaType> defaultProduces,
+ List<MediaType> defaultConsumes) {
+ //CHECKSTYLE:ON
+ this(theResourceClass, theServiceClass, theRoot, enableStatic, bus);
+ if (defaultProduces != null) {
+ this.defaultProduces = defaultProduces;
+ }
+ if (defaultConsumes != null) {
+ this.defaultConsumes = defaultConsumes;
+ }
+ }
+ public ClassResourceInfo(Class<?> theResourceClass, Class<?> theServiceClass,
+ boolean theRoot, boolean enableStatic, boolean createdFromModel, Bus bus) {
+ this(theResourceClass, theServiceClass, theRoot, enableStatic, bus);
+ this.createdFromModel = createdFromModel;
+ }
+ //CHECKSTYLE:OFF
+ public ClassResourceInfo(Class<?> theResourceClass, Class<?> theServiceClass,
+ boolean theRoot, boolean enableStatic, boolean createdFromModel,
+ String consumesTypes, String producesTypes, Bus bus) {
+ //CHECKSTYLE:ON
+ this(theResourceClass, theServiceClass, theRoot, enableStatic, createdFromModel, bus);
+ this.consumesTypes = consumesTypes;
+ this.producesTypes = producesTypes;
+ }
+
+ // The following constructors are used by tests only
+ public ClassResourceInfo(Class<?> theResourceClass) {
+ this(theResourceClass, true);
+ }
+
+ public ClassResourceInfo(Class<?> theResourceClass, boolean theRoot) {
+ this(theResourceClass, theResourceClass, theRoot);
+ }
+
+ public ClassResourceInfo(Class<?> theResourceClass, Class<?> theServiceClass) {
+ this(theResourceClass, theServiceClass, false);
+ }
+
+ public ClassResourceInfo(Class<?> theResourceClass, Class<?> theServiceClass, boolean theRoot) {
+ this(theResourceClass, theServiceClass, theRoot, false, BusFactory.getDefaultBus(true));
+ }
+
+ public ClassResourceInfo findResource(Class<?> typedClass, Class<?> instanceClass) {
+ instanceClass = enableStatic ? typedClass : instanceClass;
+ SubresourceKey key = new SubresourceKey(typedClass, instanceClass);
+ return subResources.get(key);
+ }
+
+ @Override
+ public boolean contextsAvailable() {
+ // avoid re-injecting the contexts if the root acts as subresource
+ return super.contextsAvailable() && (isRoot() || parent != null);
+ }
+
+ public ClassResourceInfo getSubResource(Class<?> typedClass, Class<?> instanceClass) {
+ return getSubResource(typedClass, instanceClass, null, enableStatic, null);
+ }
+
+ public ClassResourceInfo getSubResource(Class<?> typedClass, Class<?> instanceClass, Object instance) {
+ instanceClass = enableStatic && typedClass != Object.class ? typedClass : instanceClass;
+ return getSubResource(typedClass, instanceClass, instance, enableStatic, JAXRSUtils.getCurrentMessage());
+ }
+
+ public ClassResourceInfo getSubResource(Class<?> typedClass,
+ Class<?> instanceClass,
+ Object instance,
+ boolean resolveContexts,
+ Message message) {
+
+ SubresourceKey key = new SubresourceKey(typedClass, instanceClass);
+ ClassResourceInfo cri = subResources.get(key);
+ if (cri == null) {
+ cri = ResourceUtils.createClassResourceInfo(typedClass, instanceClass, this, false, resolveContexts,
+ getBus());
+ if (cri != null) {
+ if (message != null) {
+ cri.initBeanParamInfo(ServerProviderFactory.getInstance(message));
+ }
+ subResources.putIfAbsent(key, cri);
+ }
+ }
+ // this branch will run only if ResourceContext is used
+ // or static resolution is enabled for subresources initialized
+ // from within singleton root resources (not default)
+ if (resolveContexts && cri != null && cri.isSingleton() && instance != null && cri.contextsAvailable()) {
+ synchronized (this) {
+ if (!injectedSubInstances.contains(instance.toString())) {
+ Application app = null;
+ if (message != null) {
+ ProviderInfo<?> appProvider =
+ (ProviderInfo<?>)message.getExchange().getEndpoint().get(Application.class.getName());
+ if (appProvider != null) {
+ app = (Application)appProvider.getProvider();
+ }
+ }
+ InjectionUtils.injectContextProxiesAndApplication(cri, instance, app, null);
+ injectedSubInstances.add(instance.toString());
+ }
+ }
+ }
+
+ return cri;
+ }
+
+ public void addSubClassResourceInfo(ClassResourceInfo cri) {
+ subResources.putIfAbsent(new SubresourceKey(cri.getResourceClass(),
+ cri.getServiceClass()),
+ cri);
+ }
+
+ public Collection<ClassResourceInfo> getSubResources() {
+ return Collections.unmodifiableCollection(subResources.values());
+ }
+
+ public Set<String> getNameBindings() {
+ if (parent == null) {
+ return nameBindings;
+ }
+ return parent.getNameBindings();
+ }
+
+ public void setNameBindings(Set<String> names) {
+ nameBindings = names;
+ }
+
+ public Set<String> getAllowedMethods() {
+ Set<String> methods = new HashSet<>();
+ for (OperationResourceInfo o : methodDispatcher.getOperationResourceInfos()) {
+ String method = o.getHttpMethod();
+ if (method != null) {
+ methods.add(method);
+ }
+ }
+ return methods;
+ }
+
+
+
+ public URITemplate getURITemplate() {
+ return uriTemplate;
+ }
+
+ public void setURITemplate(URITemplate u) {
+ uriTemplate = u;
+ }
+
+ public MethodDispatcher getMethodDispatcher() {
+ return methodDispatcher;
+ }
+
+ public void setMethodDispatcher(MethodDispatcher md) {
+ methodDispatcher = md;
+ }
+
+ public boolean hasSubResources() {
+ return !subResources.isEmpty();
+ }
+
+
+ public boolean isCreatedFromModel() {
+ return createdFromModel;
+ }
+
+ public ResourceProvider getResourceProvider() {
+ return resourceProvider;
+ }
+
+ public void setResourceProvider(ResourceProvider rp) {
+ resourceProvider = rp;
+ }
+
+ public List<MediaType> getProduceMime() {
+ if (producesTypes != null) {
+ return JAXRSUtils.parseMediaTypes(producesTypes);
+ }
+ Produces produces = AnnotationUtils.getClassAnnotation(getServiceClass(), Produces.class);
+ if (produces != null || parent == null) {
+ return JAXRSUtils.getProduceTypes(produces, defaultProduces);
+ }
+ return parent.getProduceMime();
+ }
+
+ public List<MediaType> getConsumeMime() {
+ if (consumesTypes != null) {
+ return JAXRSUtils.parseMediaTypes(consumesTypes);
+ }
+ Consumes consumes = AnnotationUtils.getClassAnnotation(getServiceClass(), Consumes.class);
+ if (consumes != null || parent == null) {
+ return JAXRSUtils.getConsumeTypes(consumes, defaultConsumes);
+ }
+ return parent.getConsumeMime();
+ }
+
+ public Path getPath() {
+ return AnnotationUtils.getClassAnnotation(getServiceClass(), Path.class);
+ }
+
+ @Override
+ public boolean isSingleton() {
+ if (parent == null) {
+ return resourceProvider != null && resourceProvider.isSingleton();
+ }
+ return parent.isSingleton();
+ }
+
+ public void setParent(ClassResourceInfo parent) {
+ this.parent = parent;
+ }
+
+ public ClassResourceInfo getParent() {
+ return parent;
+ }
+
+ public void initBeanParamInfo(ServerProviderFactory factory) {
+ if (factory != null) {
+ Set<OperationResourceInfo> oris = getMethodDispatcher().getOperationResourceInfos();
+ for (OperationResourceInfo ori : oris) {
+ List<Parameter> params = ori.getParameters();
+ for (Parameter param : params) {
+ if (param.getType() == ParameterType.BEAN) {
+ Class<?> cls = ori.getMethodToInvoke().getParameterTypes()[param.getIndex()];
+ BeanParamInfo bpi = new BeanParamInfo(cls, getBus());
+ factory.addBeanParamInfo(bpi);
+ }
+ }
+ }
+ List<Method> methods = super.getParameterMethods();
+ for (Method m : methods) {
+ if (m.getAnnotation(BeanParam.class) != null) {
+ BeanParamInfo bpi = new BeanParamInfo(m.getParameterTypes()[0], getBus());
+ factory.addBeanParamInfo(bpi);
+ }
+ }
+ List<Field> fields = super.getParameterFields();
+ for (Field f : fields) {
+ if (f.getAnnotation(BeanParam.class) != null) {
+ BeanParamInfo bpi = new BeanParamInfo(f.getType(), getBus());
+ factory.addBeanParamInfo(bpi);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void clearThreadLocalProxies() {
+ super.clearThreadLocalProxies();
+ if (!injectedSubInstances.isEmpty()) {
+ for (ClassResourceInfo sub : subResources.values()) {
+ if (sub != this) {
+ sub.clearThreadLocalProxies();
+ }
+ }
+ }
+ }
+
+ public void injectContexts(Object resourceObject, OperationResourceInfo ori, Message inMessage) {
+ final boolean contextsAvailable = contextsAvailable();
+ final boolean paramsAvailable = paramsAvailable();
+ if (contextsAvailable || paramsAvailable) {
+ Object realResourceObject = ClassHelper.getRealObject(resourceObject);
+ if (paramsAvailable) {
+ JAXRSUtils.injectParameters(ori, this, realResourceObject, inMessage);
+ }
+ if (contextsAvailable) {
+ InjectionUtils.injectContexts(realResourceObject, this, inMessage);
+ }
+ }
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/model/URITemplate.java b/transform/src/patch/java/org/apache/cxf/jaxrs/model/URITemplate.java
new file mode 100644
index 0000000..85fdd7e
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/model/URITemplate.java
@@ -0,0 +1,627 @@
+/**
+ * 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.jaxrs.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.PathSegment;
+
+import org.apache.cxf.common.util.SystemPropertyAction;
+import org.apache.cxf.jaxrs.utils.HttpUtils;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+
+public final class URITemplate {
+
+ public static final String TEMPLATE_PARAMETERS = "jaxrs.template.parameters";
+ public static final String URI_TEMPLATE = "jaxrs.template.uri";
+ public static final String LIMITED_REGEX_SUFFIX = "(/.*)?";
+ public static final String FINAL_MATCH_GROUP = "FINAL_MATCH_GROUP";
+ private static final String DEFAULT_PATH_VARIABLE_REGEX = "([^/]+?)";
+ private static final String CHARACTERS_TO_ESCAPE = ".*+$()";
+ private static final String SLASH = "/";
+ private static final String SLASH_QUOTE = "/;";
+ private static final int MAX_URI_TEMPLATE_CACHE_SIZE =
+ SystemPropertyAction.getInteger("org.apache.cxf.jaxrs.max_uri_template_cache_size", 2000);
+ private static final Map<String, URITemplate> URI_TEMPLATE_CACHE = new ConcurrentHashMap<>();
+
+ private final String template;
+ private final List<String> variables = new ArrayList<>();
+ private final List<String> customVariables = new ArrayList<>();
+ private final Pattern templateRegexPattern;
+ private final String literals;
+ private final List<UriChunk> uriChunks;
+
+ public URITemplate(String theTemplate) {
+ template = theTemplate;
+ StringBuilder literalChars = new StringBuilder();
+ StringBuilder patternBuilder = new StringBuilder();
+ CurlyBraceTokenizer tok = new CurlyBraceTokenizer(template);
+ uriChunks = new ArrayList<>();
+ while (tok.hasNext()) {
+ String templatePart = tok.next();
+ UriChunk chunk = UriChunk.createUriChunk(templatePart);
+ uriChunks.add(chunk);
+ if (chunk instanceof Literal) {
+ String encodedValue = HttpUtils.encodePartiallyEncoded(chunk.getValue(), false);
+ String substr = escapeCharacters(encodedValue);
+ literalChars.append(substr);
+ patternBuilder.append(substr);
+ } else if (chunk instanceof Variable) {
+ Variable var = (Variable)chunk;
+ variables.add(var.getName());
+ String pattern = var.getPattern();
+ if (pattern != null) {
+ customVariables.add(var.getName());
+ // Add parenthesis to the pattern to identify a regex in the pattern,
+ // however do not add them if they already exist since that will cause the Matcher
+ // to create extraneous values. Parens identify a group so multiple parens would
+ // indicate multiple groups.
+ if (pattern.startsWith("(") && pattern.endsWith(")")) {
+ patternBuilder.append(pattern);
+ } else {
+ patternBuilder.append('(');
+ patternBuilder.append(pattern);
+ patternBuilder.append(')');
+ }
+ } else {
+ patternBuilder.append(DEFAULT_PATH_VARIABLE_REGEX);
+ }
+ }
+ }
+ literals = literalChars.toString();
+
+ int endPos = patternBuilder.length() - 1;
+ boolean endsWithSlash = (endPos >= 0) && patternBuilder.charAt(endPos) == '/';
+ if (endsWithSlash) {
+ patternBuilder.deleteCharAt(endPos);
+ }
+ patternBuilder.append(LIMITED_REGEX_SUFFIX);
+
+ templateRegexPattern = Pattern.compile(patternBuilder.toString());
+ }
+
+ public String getLiteralChars() {
+ return literals;
+ }
+
+ public String getValue() {
+ return template;
+ }
+
+ public String getPatternValue() {
+ return templateRegexPattern.toString();
+ }
+
+ /**
+ * List of all variables in order of appearance in template.
+ *
+ * @return unmodifiable list of variable names w/o patterns, e.g. for "/foo/{v1:\\d}/{v2}" returned list
+ * is ["v1","v2"].
+ */
+ public List<String> getVariables() {
+ return Collections.unmodifiableList(variables);
+ }
+
+ /**
+ * List of variables with patterns (regexps). List is subset of elements from {@link #getVariables()}.
+ *
+ * @return unmodifiable list of variables names w/o patterns.
+ */
+ public List<String> getCustomVariables() {
+ return Collections.unmodifiableList(customVariables);
+ }
+
+ private static String escapeCharacters(String expression) {
+
+ int length = expression.length();
+ int i = 0;
+ char ch = ' ';
+ for (; i < length; ++i) {
+ ch = expression.charAt(i);
+ if (isReservedCharacter(ch)) {
+ break;
+ }
+ }
+
+ if (i == length) {
+ return expression;
+ }
+
+ // Allows for up to 8 escaped characters before we start creating more
+ // StringBuilders. 8 is an arbitrary limit, but it seems to be
+ // sufficient in most cases.
+ StringBuilder sb = new StringBuilder(length + 8);
+ sb.append(expression, 0, i);
+ sb.append('\\');
+ sb.append(ch);
+ ++i;
+ for (; i < length; ++i) {
+ ch = expression.charAt(i);
+ if (isReservedCharacter(ch)) {
+ sb.append('\\');
+ }
+ sb.append(ch);
+ }
+ return sb.toString();
+ }
+
+ private static boolean isReservedCharacter(char ch) {
+ return CHARACTERS_TO_ESCAPE.indexOf(ch) != -1;
+ }
+
+ public boolean match(String uri, MultivaluedMap<String, String> templateVariableToValue) {
+
+ if (uri == null) {
+ return (templateRegexPattern == null) ? true : false;
+ }
+
+ if (templateRegexPattern == null) {
+ return false;
+ }
+
+ Matcher m = templateRegexPattern.matcher(uri);
+ if (!m.matches() || template.equals(SLASH) && uri.startsWith(SLASH_QUOTE)) {
+ if (uri.contains(";")) {
+ // we might be trying to match one or few path segments
+ // containing matrix
+ // parameters against a clear path segment as in @Path("base").
+ List<PathSegment> pList = JAXRSUtils.getPathSegments(template, false);
+ List<PathSegment> uList = JAXRSUtils.getPathSegments(uri, false);
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < uList.size(); i++) {
+ final String segment;
+ if (pList.size() > i && pList.get(i).getPath().indexOf('{') == -1) {
+ segment = uList.get(i).getPath();
+ } else {
+ segment = HttpUtils.fromPathSegment(uList.get(i));
+ }
+ if (!segment.isEmpty()) {
+ sb.append(SLASH);
+ }
+ sb.append(segment);
+ }
+ uri = sb.toString();
+ if (uri.isEmpty()) {
+ uri = SLASH;
+ }
+ m = templateRegexPattern.matcher(uri);
+ if (!m.matches()) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ // Assign the matched template values to template variables
+ int groupCount = m.groupCount();
+
+ int i = 1;
+ for (String name : variables) {
+ while (i <= groupCount) {
+ String value = m.group(i++);
+ if ((value == null || value.length() == 0 && i < groupCount)
+ && variables.size() + 1 < groupCount) {
+ continue;
+ }
+ templateVariableToValue.add(name, value);
+ break;
+ }
+ }
+ // The right hand side value, might be used to further resolve
+ // sub-resources.
+
+ String finalGroup = i > groupCount ? SLASH : m.group(groupCount);
+ if (finalGroup == null || finalGroup.startsWith(SLASH_QUOTE)) {
+ finalGroup = SLASH;
+ }
+
+ templateVariableToValue.putSingle(FINAL_MATCH_GROUP, finalGroup);
+
+ return true;
+ }
+
+ /**
+ * Substitutes template variables with listed values. List of values is counterpart for
+ * {@link #getVariables() list of variables}. When list of value is shorter than variables substitution
+ * is partial. When variable has pattern, value must fit to pattern, otherwise
+ * {@link IllegalArgumentException} is thrown.
+ * <p>
+ * Example1: for template "/{a}/{b}/{a}" {@link #getVariables()} returns "[a, b, a]"; providing here list
+ * of value "[foo, bar, baz]" results with "/foo/bar/baz".
+ * <p>
+ * Example2: for template "/{a}/{b}/{a}" providing list of values "[foo]" results with "/foo/{b}/{a}".
+ *
+ * @param values values for variables
+ * @return template with bound variables.
+ * @throws IllegalArgumentException when values is null, any value does not match pattern etc.
+ */
+ public String substitute(List<String> values) throws IllegalArgumentException {
+ if (values == null) {
+ throw new IllegalArgumentException("values is null");
+ }
+ Iterator<String> iter = values.iterator();
+ StringBuilder sb = new StringBuilder();
+ for (UriChunk chunk : uriChunks) {
+ if (chunk instanceof Variable) {
+ Variable var = (Variable)chunk;
+ if (iter.hasNext()) {
+ String value = iter.next();
+ if (!var.matches(value)) {
+ throw new IllegalArgumentException("Value '" + value + "' does not match variable "
+ + var.getName() + " with pattern "
+ + var.getPattern());
+ }
+ sb.append(value);
+ } else {
+ sb.append(var);
+ }
+ } else {
+ sb.append(chunk);
+ }
+ }
+ return sb.toString();
+ }
+
+ String substitute(Map<String, ? extends Object> valuesMap) throws IllegalArgumentException {
+ return this.substitute(valuesMap, Collections.<String>emptySet(), false);
+ }
+
+ /**
+ * Substitutes template variables with mapped values. Variables are mapped to values; if not all variables
+ * are bound result will still contain variables. Note that all variables with the same name are replaced
+ * by one value.
+ * <p>
+ * Example: for template "/{a}/{b}/{a}" {@link #getVariables()} returns "[a, b, a]"; providing here
+ * mapping "[a: foo, b: bar]" results with "/foo/bar/foo" (full substitution) and for mapping "[b: baz]"
+ * result is "{a}/baz/{a}" (partial substitution).
+ *
+ * @param valuesMap map variables to their values; on each value Object.toString() is called.
+ * @return template with bound variables.
+ */
+ public String substitute(Map<String, ? extends Object> valuesMap,
+ Set<String> encodePathSlashVars,
+ boolean allowUnresolved) throws IllegalArgumentException {
+ if (valuesMap == null) {
+ throw new IllegalArgumentException("valuesMap is null");
+ }
+ StringBuilder sb = new StringBuilder();
+ for (UriChunk chunk : uriChunks) {
+ if (chunk instanceof Variable) {
+ Variable var = (Variable)chunk;
+ Object value = valuesMap.get(var.getName());
+ if (value != null) {
+ String sval = value.toString();
+ if (!var.matches(sval)) {
+ throw new IllegalArgumentException("Value '" + sval + "' does not match variable "
+ + var.getName() + " with pattern "
+ + var.getPattern());
+ }
+ if (encodePathSlashVars.contains(var.getName())) {
+ sval = sval.replaceAll("/", "%2F");
+ }
+ sb.append(sval);
+ } else if (allowUnresolved) {
+ sb.append(chunk);
+ } else {
+ throw new IllegalArgumentException("Template variable " + var.getName()
+ + " has no matching value");
+ }
+ } else {
+ sb.append(chunk);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Encoded literal characters surrounding template variables,
+ * ex. "a {id} b" will be encoded to "a%20{id}%20b"
+ * @return encoded value
+ */
+ public String encodeLiteralCharacters(boolean isQuery) {
+ final float encodedRatio = 1.5f;
+ StringBuilder sb = new StringBuilder((int)(encodedRatio * template.length()));
+ for (UriChunk chunk : uriChunks) {
+ String val = chunk.getValue();
+ if (chunk instanceof Literal) {
+ sb.append(HttpUtils.encodePartiallyEncoded(val, isQuery));
+ } else {
+ sb.append(val);
+ }
+ }
+ return sb.toString();
+ }
+
+ public static URITemplate createTemplate(Path path) {
+
+ return createTemplate(path == null ? null : path.value());
+ }
+
+ public static URITemplate createTemplate(String pathValue) {
+ if (pathValue == null) {
+ pathValue = "/";
+ } else if (!pathValue.startsWith("/")) {
+ pathValue = "/" + pathValue;
+ }
+ return createExactTemplate(pathValue);
+ }
+
+ public static URITemplate createExactTemplate(String pathValue) {
+ URITemplate template = URI_TEMPLATE_CACHE.get(pathValue);
+ if (template == null) {
+ template = new URITemplate(pathValue);
+ if (URI_TEMPLATE_CACHE.size() >= MAX_URI_TEMPLATE_CACHE_SIZE) {
+ URI_TEMPLATE_CACHE.clear();
+ }
+ URI_TEMPLATE_CACHE.put(pathValue, template);
+ }
+ return template;
+ }
+
+ public static int compareTemplates(URITemplate t1, URITemplate t2) {
+ int l1 = t1.getLiteralChars().length();
+ int l2 = t2.getLiteralChars().length();
+ // descending order
+ int result = l1 < l2 ? 1 : l1 > l2 ? -1 : 0;
+ if (result == 0) {
+ int g1 = t1.getVariables().size();
+ int g2 = t2.getVariables().size();
+ // descending order
+ result = g1 < g2 ? 1 : g1 > g2 ? -1 : 0;
+ if (result == 0) {
+ int gCustom1 = t1.getCustomVariables().size();
+ int gCustom2 = t2.getCustomVariables().size();
+ result = gCustom1 < gCustom2 ? 1 : gCustom1 > gCustom2 ? -1 : 0;
+ if (result == 0) {
+ result = t1.getPatternValue().compareTo(t2.getPatternValue());
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Stringified part of URI. Chunk is not URI segment; chunk can span over multiple URI segments or one URI
+ * segments can have multiple chunks. Chunk is used to decompose URI of {@link URITemplate} into literals
+ * and variables. Example: "foo/bar/{baz}{blah}" is decomposed into chunks: "foo/bar", "{baz}" and
+ * "{blah}".
+ */
+ private abstract static class UriChunk {
+ /**
+ * Creates object form string.
+ *
+ * @param uriChunk stringified uri chunk
+ * @return If param has variable form then {@link Variable} instance is created, otherwise chunk is
+ * treated as {@link Literal}.
+ */
+ public static UriChunk createUriChunk(String uriChunk) {
+ if (uriChunk == null || "".equals(uriChunk)) {
+ throw new IllegalArgumentException("uriChunk is empty");
+ }
+ UriChunk uriChunkRepresentation = Variable.create(uriChunk);
+ if (uriChunkRepresentation == null) {
+ uriChunkRepresentation = Literal.create(uriChunk);
+ }
+ return uriChunkRepresentation;
+ }
+
+ public abstract String getValue();
+
+ @Override
+ public String toString() {
+ return getValue();
+ }
+ }
+
+ private static final class Literal extends UriChunk {
+ private String value;
+
+ private Literal() {
+ // empty constructor
+ }
+
+ public static Literal create(String uriChunk) {
+ if (uriChunk == null || "".equals(uriChunk)) {
+ throw new IllegalArgumentException("uriChunk is empty");
+ }
+ Literal literal = new Literal();
+ literal.value = uriChunk;
+ return literal;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ }
+
+ /**
+ * Variable of URITemplate. Variable has either "{varname:pattern}" syntax or "{varname}".
+ */
+ private static final class Variable extends UriChunk {
+ private static final Pattern VARIABLE_PATTERN = Pattern.compile("(\\w[-\\w\\.]*[ ]*)(\\:(.+))?");
+ private String name;
+ private Pattern pattern;
+
+ private Variable() {
+ // empty constructor
+ }
+
+ /**
+ * Creates variable from stringified part of URI.
+ *
+ * @param uriChunk uriChunk chunk that depicts variable
+ * @return Variable if variable was successfully created; null if uriChunk was not a variable
+ */
+ public static Variable create(String uriChunk) {
+ Variable newVariable = new Variable();
+ if (uriChunk == null || "".equals(uriChunk)) {
+ return null;
+ }
+ if (CurlyBraceTokenizer.insideBraces(uriChunk)) {
+ uriChunk = CurlyBraceTokenizer.stripBraces(uriChunk).trim();
+ Matcher matcher = VARIABLE_PATTERN.matcher(uriChunk);
+ if (matcher.matches()) {
+ newVariable.name = matcher.group(1).trim();
+ if (matcher.group(2) != null && matcher.group(3) != null) {
+ String patternExpression = matcher.group(3).trim();
+ newVariable.pattern = Pattern.compile(patternExpression);
+ }
+ return newVariable;
+ }
+ }
+ return null;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getPattern() {
+ return pattern != null ? pattern.pattern() : null;
+ }
+
+ /**
+ * Checks whether value matches variable. If variable has pattern its checked against, otherwise true
+ * is returned.
+ *
+ * @param value value of variable
+ * @return true if value is valid for variable, false otherwise.
+ */
+ public boolean matches(String value) {
+ if (pattern == null) {
+ return true;
+ }
+ return pattern.matcher(value).matches();
+ }
+
+ @Override
+ public String getValue() {
+ if (pattern != null) {
+ return "{" + name + ":" + pattern + "}";
+ }
+ return "{" + name + "}";
+ }
+ }
+
+ /**
+ * Splits string into parts inside and outside curly braces. Nested curly braces are ignored and treated
+ * as part inside top-level curly braces. Example: string "foo{bar{baz}}blah" is split into three tokens,
+ * "foo","{bar{baz}}" and "blah". When closed bracket is missing, whole unclosed part is returned as one
+ * token, e.g.: "foo{bar" is split into "foo" and "{bar". When opening bracket is missing, closing
+ * bracket is ignored and taken as part of current token e.g.: "foo{bar}baz}blah" is split into "foo",
+ * "{bar}" and "baz}blah".
+ * <p>
+ * This is helper class for {@link URITemplate} that enables recurring literals appearing next to regular
+ * expressions e.g. "/foo/{zipcode:[0-9]{5}}/". Nested expressions with closed sections, like open-closed
+ * brackets causes expression to be out of regular grammar (is context-free grammar) which are not
+ * supported by Java regexp version.
+ */
+ static class CurlyBraceTokenizer {
+
+ private List<String> tokens = new ArrayList<>();
+ private int tokenIdx;
+
+ CurlyBraceTokenizer(String string) {
+ boolean outside = true;
+ int level = 0;
+ int lastIdx = 0;
+ int idx;
+ for (idx = 0; idx < string.length(); idx++) {
+ if (string.charAt(idx) == '{') {
+ if (outside) {
+ if (lastIdx < idx) {
+ tokens.add(string.substring(lastIdx, idx));
+ }
+ lastIdx = idx;
+ outside = false;
+ } else {
+ level++;
+ }
+ } else if (string.charAt(idx) == '}' && !outside) {
+ if (level > 0) {
+ level--;
+ } else {
+ if (lastIdx < idx) {
+ tokens.add(string.substring(lastIdx, idx + 1));
+ }
+ lastIdx = idx + 1;
+ outside = true;
+ }
+ }
+ }
+ if (lastIdx < idx) {
+ tokens.add(string.substring(lastIdx, idx));
+ }
+ }
+
+ /**
+ * Token is enclosed by curly braces.
+ *
+ * @param token
+ * text to verify
+ * @return true if enclosed, false otherwise.
+ */
+ public static boolean insideBraces(String token) {
+ return token.charAt(0) == '{' && token.charAt(token.length() - 1) == '}';
+ }
+
+ /**
+ * Strips token from enclosed curly braces. If token is not enclosed method
+ * has no side effect.
+ *
+ * @param token
+ * text to verify
+ * @return text stripped from curly brace begin-end pair.
+ */
+ public static String stripBraces(String token) {
+ if (insideBraces(token)) {
+ return token.substring(1, token.length() - 1);
+ }
+ return token;
+ }
+
+ public boolean hasNext() {
+ return tokens.size() > tokenIdx;
+ }
+
+ public String next() {
+ if (hasNext()) {
+ return tokens.get(tokenIdx++);
+ }
+ throw new IllegalStateException("no more elements");
+ }
+ }
+}
+
+
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/provider/CachingMessageBodyReader.java b/transform/src/patch/java/org/apache/cxf/jaxrs/provider/CachingMessageBodyReader.java
new file mode 100644
index 0000000..ecdc8f1
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/provider/CachingMessageBodyReader.java
@@ -0,0 +1,100 @@
+/**
+ * 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.jaxrs.provider;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.List;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.cxf.jaxrs.utils.ExceptionUtils;
+
+@Provider
+public class CachingMessageBodyReader<T> extends AbstractCachingMessageProvider<T>
+ implements MessageBodyReader<T> {
+
+ private List<MessageBodyReader<T>> delegatingReaders;
+
+ public boolean isReadable(Class<?> type, Type gType, Annotation[] anns, MediaType mt) {
+ if (delegatingReaders != null) {
+ return getDelegatingReader(type, gType, anns, mt) != null;
+ }
+ return isProviderKeyNotSet();
+ }
+
+ private MessageBodyReader<T> getDelegatingReader(Class<?> type, Type gType, Annotation[] anns, MediaType mt) {
+ for (MessageBodyReader<T> reader: delegatingReaders) {
+ if (reader.isReadable(type, gType, anns, mt)) {
+ return reader;
+ }
+ }
+ return null;
+ }
+
+ public T readFrom(Class<T> type, Type gType, Annotation[] anns, MediaType mt,
+ MultivaluedMap<String, String> theheaders, InputStream is)
+ throws IOException, WebApplicationException {
+ this.setObject(
+ getReader(type, gType, anns, mt).readFrom(type, gType, anns, mt, theheaders, is));
+ return getObject();
+ }
+
+
+ protected MessageBodyReader<T> getReader(Class<?> type, Type gType, Annotation[] anns, MediaType mt) {
+ if (delegatingReaders != null) {
+ return getDelegatingReader(type, gType, anns, mt);
+ }
+ final MessageBodyReader<T> r;
+
+ mc.put(ACTIVE_JAXRS_PROVIDER_KEY, this);
+ try {
+ @SuppressWarnings("unchecked")
+ Class<T> actualType = (Class<T>)type;
+ r = mc.getProviders().getMessageBodyReader(actualType, gType, anns, mt);
+ } finally {
+ mc.put(ACTIVE_JAXRS_PROVIDER_KEY, null);
+ }
+
+ if (r == null) {
+ org.apache.cxf.common.i18n.Message message =
+ new org.apache.cxf.common.i18n.Message("NO_MSG_READER", BUNDLE, type);
+ LOG.severe(message.toString());
+ throw ExceptionUtils.toNotAcceptableException(null, null);
+ }
+ return r;
+ }
+
+ public void setDelegatingReader(MessageBodyReader<T> reader) {
+ this.delegatingReaders = Collections.singletonList(reader);
+ }
+
+ public void setDelegatingReaders(List<MessageBodyReader<T>> readers) {
+ this.delegatingReaders = readers;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/provider/CachingMessageBodyWriter.java b/transform/src/patch/java/org/apache/cxf/jaxrs/provider/CachingMessageBodyWriter.java
new file mode 100644
index 0000000..21eab42
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/provider/CachingMessageBodyWriter.java
@@ -0,0 +1,102 @@
+/**
+ * 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.jaxrs.provider;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.List;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.cxf.jaxrs.utils.ExceptionUtils;
+
+@Provider
+public class CachingMessageBodyWriter<T> extends AbstractCachingMessageProvider<T>
+ implements MessageBodyWriter<T> {
+
+ private List<MessageBodyWriter<T>> delegatingWriters;
+
+ public long getSize(T t, Class<?> type, Type gType, Annotation[] anns, MediaType mediaType) {
+ return -1;
+ }
+
+ public boolean isWriteable(Class<?> type, Type gType, Annotation[] anns, MediaType mt) {
+ if (delegatingWriters != null) {
+ return getDelegatingWriter(type, gType, anns, mt) != null;
+ }
+ return isProviderKeyNotSet();
+ }
+
+ private MessageBodyWriter<T> getDelegatingWriter(Class<?> type, Type gType, Annotation[] anns, MediaType mt) {
+ for (MessageBodyWriter<T> writer: delegatingWriters) {
+ if (writer.isWriteable(type, gType, anns, mt)) {
+ return writer;
+ }
+ }
+ return null;
+ }
+
+ public void writeTo(T obj, Class<?> type, Type gType, Annotation[] anns, MediaType mt,
+ MultivaluedMap<String, Object> theheaders, OutputStream os)
+ throws IOException, WebApplicationException {
+ this.setObject(obj);
+ getWriter(type, gType, anns, mt).writeTo(getObject(), type, gType, anns, mt, theheaders, os);
+ }
+
+
+ protected MessageBodyWriter<T> getWriter(Class<?> type, Type gType, Annotation[] anns, MediaType mt) {
+ if (delegatingWriters != null) {
+ return getDelegatingWriter(type, gType, anns, mt);
+ }
+ MessageBodyWriter<T> w;
+
+ mc.put(ACTIVE_JAXRS_PROVIDER_KEY, this);
+ try {
+ @SuppressWarnings("unchecked")
+ Class<T> actualType = (Class<T>)type;
+ w = mc.getProviders().getMessageBodyWriter(actualType, gType, anns, mt);
+ } finally {
+ mc.put(ACTIVE_JAXRS_PROVIDER_KEY, null);
+ }
+
+ if (w == null) {
+ org.apache.cxf.common.i18n.Message message =
+ new org.apache.cxf.common.i18n.Message("NO_MSG_WRITER", BUNDLE, type);
+ LOG.severe(message.toString());
+ throw ExceptionUtils.toInternalServerErrorException(null, null);
+ }
+ return w;
+ }
+
+ public void setDelegatingWriter(MessageBodyWriter<T> writer) {
+ this.delegatingWriters = Collections.singletonList(writer);
+ }
+
+ public void setDelegatingWriters(List<MessageBodyWriter<T>> writers) {
+ this.delegatingWriters = writers;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/provider/DataSourceProvider.java b/transform/src/patch/java/org/apache/cxf/jaxrs/provider/DataSourceProvider.java
new file mode 100644
index 0000000..efecac4
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/provider/DataSourceProvider.java
@@ -0,0 +1,110 @@
+/**
+ * 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.jaxrs.provider;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.logging.Logger;
+
+import javax.activation.DataHandler;
+import javax.activation.DataSource;
+import javax.activation.FileDataSource;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.jaxrs.ext.multipart.InputStreamDataSource;
+import org.apache.cxf.jaxrs.utils.ExceptionUtils;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+
+@Provider
+public class DataSourceProvider<T> implements MessageBodyReader<T>, MessageBodyWriter<T> {
+ protected static final Logger LOG = LogUtils.getL7dLogger(DataSourceProvider.class);
+ private boolean useDataSourceContentType;
+
+ public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mt) {
+ return isSupported(type);
+ }
+
+ public T readFrom(Class<T> cls, Type genericType, Annotation[] annotations,
+ MediaType type,
+ MultivaluedMap<String, String> headers, InputStream is)
+ throws IOException {
+
+ final DataSource ds;
+ if (cls == FileDataSource.class) {
+ File file = new BinaryDataProvider<File>().readFrom(File.class, File.class, annotations, type, headers, is);
+ ds = new FileDataSource(file);
+ } else if (cls == DataSource.class || cls == DataHandler.class) {
+ ds = new InputStreamDataSource(is, type.toString());
+ } else {
+ LOG.warning("Unsupported DataSource class: " + cls.getName());
+ throw ExceptionUtils.toWebApplicationException(null, null);
+ }
+ return cls.cast(DataSource.class.isAssignableFrom(cls) ? ds : new DataHandler(ds));
+ }
+
+ public long getSize(T t, Class<?> type, Type genericType, Annotation[] annotations,
+ MediaType mt) {
+ return -1;
+ }
+
+ public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mt) {
+ return isSupported(type);
+ }
+
+ private static boolean isSupported(Class<?> type) {
+ return DataSource.class.isAssignableFrom(type) || DataHandler.class.isAssignableFrom(type);
+ }
+
+ public void writeTo(T src, Class<?> cls, Type genericType, Annotation[] annotations,
+ MediaType type, MultivaluedMap<String, Object> headers, OutputStream os)
+ throws IOException {
+ DataSource ds = DataSource.class.isAssignableFrom(cls)
+ ? (DataSource)src : ((DataHandler)src).getDataSource();
+ if (useDataSourceContentType) {
+ setContentTypeIfNeeded(type, headers, ds.getContentType());
+ }
+ IOUtils.copyAndCloseInput(ds.getInputStream(), os);
+ }
+
+ private void setContentTypeIfNeeded(MediaType type,
+ MultivaluedMap<String, Object> headers, String ct) {
+
+ if (!StringUtils.isEmpty(ct) && !type.equals(JAXRSUtils.toMediaType(ct))) {
+ headers.putSingle("Content-Type", ct);
+ }
+ }
+
+ public void setUseDataSourceContentType(boolean useDataSourceContentType) {
+ this.useDataSourceContentType = useDataSourceContentType;
+ }
+
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/provider/FormEncodingProvider.java b/transform/src/patch/java/org/apache/cxf/jaxrs/provider/FormEncodingProvider.java
new file mode 100644
index 0000000..0344592
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/provider/FormEncodingProvider.java
@@ -0,0 +1,228 @@
+/**
+ * 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.jaxrs.provider;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Encoded;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.cxf.jaxrs.ext.MessageContext;
+import org.apache.cxf.jaxrs.ext.multipart.Attachment;
+import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
+import org.apache.cxf.jaxrs.impl.MetadataMap;
+import org.apache.cxf.jaxrs.utils.AnnotationUtils;
+import org.apache.cxf.jaxrs.utils.ExceptionUtils;
+import org.apache.cxf.jaxrs.utils.FormUtils;
+import org.apache.cxf.jaxrs.utils.HttpUtils;
+import org.apache.cxf.jaxrs.utils.multipart.AttachmentUtils;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.PhaseInterceptorChain;
+
+@Produces({"application/x-www-form-urlencoded", "multipart/form-data" })
+@Consumes({"application/x-www-form-urlencoded", "multipart/form-data" })
+@Provider
+public class FormEncodingProvider<T> extends AbstractConfigurableProvider
+ implements MessageBodyReader<T>, MessageBodyWriter<T> {
+
+ private FormValidator validator;
+ @Context private MessageContext mc;
+ private String attachmentDir;
+ private String attachmentThreshold;
+ private String attachmentMaxSize;
+
+ private boolean expectEncoded;
+
+ public FormEncodingProvider() {
+
+ }
+
+ public FormEncodingProvider(boolean expectEncoded) {
+ this.expectEncoded = expectEncoded;
+ }
+
+ public void setExpectedEncoded(boolean expect) {
+ this.expectEncoded = expect;
+ }
+
+ public void setAttachmentDirectory(String dir) {
+ attachmentDir = dir;
+ }
+
+ public void setAttachmentThreshold(String threshold) {
+ attachmentThreshold = threshold;
+ }
+
+ public void setAttachmentMaxSize(String maxSize) {
+ attachmentMaxSize = maxSize;
+ }
+
+ public void setValidator(FormValidator formValidator) {
+ validator = formValidator;
+ }
+
+ public boolean isReadable(Class<?> type, Type genericType,
+ Annotation[] annotations, MediaType mt) {
+ return isSupported(type, mt);
+ }
+
+ public T readFrom(
+ Class<T> clazz, Type genericType, Annotation[] annotations, MediaType mt,
+ MultivaluedMap<String, String> headers, InputStream is)
+ throws IOException {
+ if (is == null) {
+ return null;
+ }
+ try {
+ if (mt.isCompatible(MediaType.MULTIPART_FORM_DATA_TYPE)) {
+ MultipartBody body = AttachmentUtils.getMultipartBody(mc);
+ if (MultipartBody.class.isAssignableFrom(clazz)) {
+ return clazz.cast(body);
+ } else if (Attachment.class.isAssignableFrom(clazz)) {
+ return clazz.cast(body.getRootAttachment());
+ }
+ }
+
+ MultivaluedMap<String, String> params = createMap(clazz);
+ populateMap(params, annotations, is, mt, !keepEncoded(annotations));
+ validateMap(params);
+
+ persistParamsOnMessage(params);
+
+ return getFormObject(clazz, params);
+ } catch (WebApplicationException e) {
+ throw e;
+ } catch (Exception e) {
+ throw ExceptionUtils.toBadRequestException(e, null);
+ }
+ }
+
+ protected boolean keepEncoded(Annotation[] anns) {
+ return AnnotationUtils.getAnnotation(anns, Encoded.class) != null
+ || expectEncoded;
+ }
+
+ protected void persistParamsOnMessage(MultivaluedMap<String, String> params) {
+ Message message = PhaseInterceptorChain.getCurrentMessage();
+ if (message != null) {
+ message.put(FormUtils.FORM_PARAM_MAP, params);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ protected MultivaluedMap<String, String> createMap(Class<?> clazz) throws Exception {
+ if (clazz == MultivaluedMap.class || clazz == Form.class) {
+ return new MetadataMap<String, String>();
+ }
+ return (MultivaluedMap<String, String>)clazz.newInstance();
+ }
+
+ private T getFormObject(Class<T> clazz, MultivaluedMap<String, String> params) {
+ return clazz.cast(Form.class.isAssignableFrom(clazz) ? new Form(params) : params);
+ }
+
+ /**
+ * Retrieve map of parameters from the passed in message
+ */
+ protected void populateMap(MultivaluedMap<String, String> params,
+ Annotation[] anns,
+ InputStream is,
+ MediaType mt,
+ boolean decode) {
+ if (mt.isCompatible(MediaType.MULTIPART_FORM_DATA_TYPE)) {
+ MultipartBody body =
+ AttachmentUtils.getMultipartBody(mc, attachmentDir, attachmentThreshold, attachmentMaxSize);
+ FormUtils.populateMapFromMultipart(params, body, PhaseInterceptorChain.getCurrentMessage(),
+ decode);
+ } else {
+ String enc = HttpUtils.getEncoding(mt, StandardCharsets.UTF_8.name());
+
+ Object servletRequest = mc != null ? mc.getHttpServletRequest() : null;
+ if (servletRequest == null) {
+ FormUtils.populateMapFromString(params,
+ PhaseInterceptorChain.getCurrentMessage(),
+ FormUtils.readBody(is, enc),
+ enc,
+ decode);
+ } else {
+ FormUtils.populateMapFromString(params,
+ PhaseInterceptorChain.getCurrentMessage(),
+ FormUtils.readBody(is, enc),
+ enc,
+ decode,
+ (javax.servlet.http.HttpServletRequest)servletRequest);
+ }
+ }
+ }
+
+ protected void validateMap(MultivaluedMap<String, String> params) {
+ if (validator != null) {
+ validator.validate(params);
+ }
+ }
+
+ public long getSize(T t, Class<?> type,
+ Type genericType, Annotation[] annotations,
+ MediaType mediaType) {
+ return -1;
+ }
+
+ public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations,
+ MediaType mt) {
+ return isSupported(type, mt);
+ }
+
+ private static boolean isSupported(Class<?> type, MediaType mt) {
+ return (MultivaluedMap.class.isAssignableFrom(type) || Form.class.isAssignableFrom(type))
+ || ("multipart".equalsIgnoreCase(mt.getType())
+ && mt.isCompatible(MediaType.MULTIPART_FORM_DATA_TYPE)
+ && (MultivaluedMap.class.isAssignableFrom(type) || Form.class.isAssignableFrom(type)));
+ }
+
+ @SuppressWarnings("unchecked")
+ public void writeTo(T obj, Class<?> c, Type t, Annotation[] anns,
+ MediaType mt, MultivaluedMap<String, Object> headers, OutputStream os)
+ throws IOException, WebApplicationException {
+
+ MultivaluedMap<String, String> map =
+ (MultivaluedMap<String, String>)(obj instanceof Form ? ((Form)obj).asMap() : obj);
+ boolean encoded = keepEncoded(anns);
+
+ String enc = HttpUtils.getSetEncoding(mt, headers, StandardCharsets.UTF_8.name());
+
+ FormUtils.writeMapToOutputStream(map, os, enc, encoded);
+
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/provider/JAXBElementProvider.java b/transform/src/patch/java/org/apache/cxf/jaxrs/provider/JAXBElementProvider.java
new file mode 100644
index 0000000..ab27e02
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/provider/JAXBElementProvider.java
@@ -0,0 +1,635 @@
+/**
+ * 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.jaxrs.provider;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.ext.Provider;
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.Source;
+
+import org.w3c.dom.Document;
+
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.jaxrs.ext.MessageContext;
+import org.apache.cxf.jaxrs.ext.Nullable;
+import org.apache.cxf.jaxrs.ext.xml.XMLInstruction;
+import org.apache.cxf.jaxrs.ext.xml.XMLSource;
+import org.apache.cxf.jaxrs.ext.xml.XSISchemaLocation;
+import org.apache.cxf.jaxrs.ext.xml.XSLTTransform;
+import org.apache.cxf.jaxrs.utils.AnnotationUtils;
+import org.apache.cxf.jaxrs.utils.ExceptionUtils;
+import org.apache.cxf.jaxrs.utils.HttpUtils;
+import org.apache.cxf.jaxrs.utils.InjectionUtils;
+import org.apache.cxf.jaxrs.utils.JAXBUtils;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.message.Attachment;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.staxutils.DepthExceededStaxException;
+import org.apache.cxf.staxutils.StaxUtils;
+import org.apache.cxf.staxutils.transform.TransformUtils;
+
+@Produces({"application/xml", "application/*+xml", "text/xml" })
+@Consumes({"application/xml", "application/*+xml", "text/xml" })
+@Provider
+public class JAXBElementProvider<T> extends AbstractJAXBProvider<T> {
+ private static final String XML_PI_START = "<?xml version=\"1.0\" encoding=\"";
+ private static final String XML_PI_PROPERTY_RI = "com.sun.xml.bind.xmlHeaders";
+ private static final String XML_PI_PROPERTY_RI_INT = "com.sun.xml.internal.bind.xmlHeaders";
+
+ private static final String[] MARSHALLER_PROPERTIES = {
+ Marshaller.JAXB_ENCODING,
+ Marshaller.JAXB_FORMATTED_OUTPUT,
+ Marshaller.JAXB_FRAGMENT,
+ Marshaller.JAXB_NO_NAMESPACE_SCHEMA_LOCATION,
+ Marshaller.JAXB_SCHEMA_LOCATION,
+ NS_MAPPER_PROPERTY_RI,
+ NS_MAPPER_PROPERTY_RI_INT,
+ XML_PI_PROPERTY_RI,
+ XML_PI_PROPERTY_RI_INT
+ };
+
+ private Map<String, Object> mProperties = Collections.emptyMap();
+ private Map<String, String> nsPrefixes = Collections.emptyMap();
+ private String xmlResourceOffset = "";
+ private String xmlPiPropertyName;
+
+ public JAXBElementProvider() {
+
+ }
+
+ protected boolean objectFactoryOrIndexAvailable(Class<?> type) {
+ return !Document.class.isAssignableFrom(type) && super.objectFactoryOrIndexAvailable(type);
+ }
+
+ public void setXmlResourceOffset(String value) {
+ xmlResourceOffset = value;
+ }
+
+ public void setNamespacePrefixes(Map<String, String> prefixes) {
+ nsPrefixes = prefixes;
+ }
+
+ protected void setXmlPiProperty(Marshaller ms, String value) throws Exception {
+ if (xmlPiPropertyName != null) {
+ setMarshallerProp(ms, value, xmlPiPropertyName, null);
+ } else {
+ setMarshallerProp(ms, value, XML_PI_PROPERTY_RI, XML_PI_PROPERTY_RI_INT);
+ }
+ }
+
+ @Override
+ protected boolean canBeReadAsJaxbElement(Class<?> type) {
+ return super.canBeReadAsJaxbElement(type)
+ && type != XMLSource.class && !Source.class.isAssignableFrom(type);
+ }
+
+ @Context
+ public void setMessageContext(MessageContext mc) {
+ super.setContext(mc);
+ }
+
+ public void setMarshallerProperties(Map<String, Object> marshallProperties) {
+ mProperties = marshallProperties;
+ }
+
+ public void setSchemaLocation(String schemaLocation) {
+ mProperties.put(Marshaller.JAXB_SCHEMA_LOCATION, schemaLocation);
+ }
+
+ public T readFrom(Class<T> type, Type genericType, Annotation[] anns, MediaType mt,
+ MultivaluedMap<String, String> headers, InputStream is)
+ throws IOException {
+ if (isPayloadEmpty(headers)) {
+ if (AnnotationUtils.getAnnotation(anns, Nullable.class) != null) {
+ return null;
+ }
+ reportEmptyContentLength();
+ }
+
+ XMLStreamReader reader = null;
+ Unmarshaller unmarshaller = null;
+ try {
+
+ boolean isCollection = InjectionUtils.isSupportedCollectionOrArray(type);
+ Class<?> theGenericType = isCollection ? InjectionUtils.getActualType(genericType) : type;
+ Class<?> theType = getActualType(theGenericType, genericType, anns);
+
+ unmarshaller = createUnmarshaller(theType, genericType, isCollection);
+ addAttachmentUnmarshaller(unmarshaller);
+ Object response = null;
+ if (JAXBElement.class.isAssignableFrom(type)
+ || !isCollection && (unmarshalAsJaxbElement
+ || jaxbElementClassMap != null && jaxbElementClassMap.containsKey(theType.getName()))) {
+ try (InputStream in = IOUtils.nullOrNotEmptyStream(is)) {
+ // The return value might be "null" in case of empty stream, in this
+ // case the unmarshaller fails with javax.xml.bind.UnmarshalException instead
+ // of returning empty response.
+ if (in != null) {
+ reader = getStreamReader(in, type, mt);
+ reader = TransformUtils.createNewReaderIfNeeded(reader, in);
+
+ if (JAXBElement.class.isAssignableFrom(type) && type == theType) {
+ response = unmarshaller.unmarshal(reader);
+ } else {
+ response = unmarshaller.unmarshal(reader, theType);
+ }
+ }
+ }
+ } else {
+ response = doUnmarshal(unmarshaller, type, is, anns, mt);
+ }
+
+ if (response instanceof JAXBElement && !JAXBElement.class.isAssignableFrom(type)) {
+ response = ((JAXBElement<?>)response).getValue();
+ }
+ if (isCollection) {
+ response = ((CollectionWrapper)response).getCollectionOrArray(
+ unmarshaller, theType, type, genericType,
+ org.apache.cxf.jaxrs.utils.JAXBUtils.getAdapter(theGenericType, anns));
+ } else {
+ response = checkAdapter(response, type, anns, false);
+ }
+ return type.cast(response);
+
+ } catch (JAXBException e) {
+ handleJAXBException(e, true);
+ } catch (DepthExceededStaxException e) {
+ throw ExceptionUtils.toWebApplicationException(null, JAXRSUtils.toResponse(413));
+ } catch (WebApplicationException e) {
+ throw e;
+ } catch (Exception e) {
+ LOG.warning(ExceptionUtils.getStackTrace(e));
+ throw ExceptionUtils.toBadRequestException(e, null);
+ } finally {
+ try {
+ StaxUtils.close(reader);
+ } catch (XMLStreamException e) {
+ // Ignore
+ }
+ JAXBUtils.closeUnmarshaller(unmarshaller);
+ }
+ // unreachable
+ return null;
+ }
+
+ protected Object doUnmarshal(Unmarshaller unmarshaller, Class<?> type, InputStream is,
+ Annotation[] anns, MediaType mt)
+ throws JAXBException {
+ XMLStreamReader reader = getStreamReader(is, type, mt);
+ if (reader != null) {
+ try {
+ return unmarshalFromReader(unmarshaller, reader, anns, mt);
+ } catch (JAXBException e) {
+ throw e;
+ } finally {
+ try {
+ StaxUtils.close(reader);
+ } catch (XMLStreamException e) {
+ // Ignore
+ }
+ }
+ }
+ return unmarshalFromInputStream(unmarshaller, is, anns, mt);
+ }
+
+ protected XMLStreamReader getStreamReader(InputStream is, Class<?> type, MediaType mt) {
+ MessageContext mc = getContext();
+ XMLStreamReader reader = mc != null ? mc.getContent(XMLStreamReader.class) : null;
+ if (reader == null && mc != null) {
+ XMLInputFactory factory = (XMLInputFactory)mc.get(XMLInputFactory.class.getName());
+ if (factory != null) {
+ try {
+ reader = factory.createXMLStreamReader(is);
+ } catch (XMLStreamException e) {
+ throw ExceptionUtils.toInternalServerErrorException(
+ new RuntimeException("Can not create XMLStreamReader", e), null);
+ }
+ }
+ }
+
+ if (reader == null && is == null) {
+ reader = getStreamHandlerFromCurrentMessage(XMLStreamReader.class);
+ }
+
+ reader = createTransformReaderIfNeeded(reader, is);
+ reader = createDepthReaderIfNeeded(reader, is);
+ if (InjectionUtils.isSupportedCollectionOrArray(type)) {
+ return new JAXBCollectionWrapperReader(TransformUtils.createNewReaderIfNeeded(reader, is));
+ }
+ return reader;
+
+ }
+
+ protected Object unmarshalFromInputStream(Unmarshaller unmarshaller, InputStream is,
+ Annotation[] anns, MediaType mt)
+ throws JAXBException {
+ // Try to create the read before unmarshalling the stream
+ XMLStreamReader xmlReader = null;
+ try {
+ if (is == null) {
+ Reader reader = getStreamHandlerFromCurrentMessage(Reader.class);
+ if (reader == null) {
+ LOG.severe("No InputStream, Reader, or XMLStreamReader is available");
+ throw ExceptionUtils.toInternalServerErrorException(null, null);
+ }
+ xmlReader = StaxUtils.createXMLStreamReader(reader);
+ } else {
+ xmlReader = StaxUtils.createXMLStreamReader(is);
+ }
+ configureReaderRestrictions(xmlReader);
+ return unmarshaller.unmarshal(xmlReader);
+ } finally {
+ try {
+ StaxUtils.close(xmlReader);
+ } catch (XMLStreamException e) {
+ // Ignore
+ }
+ }
+ }
+
+ protected Object unmarshalFromReader(Unmarshaller unmarshaller, XMLStreamReader reader,
+ Annotation[] anns, MediaType mt)
+ throws JAXBException {
+ return unmarshaller.unmarshal(reader);
+ }
+
+ public void writeTo(T obj, Class<?> cls, Type genericType, Annotation[] anns,
+ MediaType m, MultivaluedMap<String, Object> headers, OutputStream os)
+ throws IOException {
+ try {
+ String encoding = HttpUtils.getSetEncoding(m, headers, StandardCharsets.UTF_8.name());
+ if (InjectionUtils.isSupportedCollectionOrArray(cls)) {
+ marshalCollection(cls, obj, genericType, encoding, os, m, anns);
+ } else {
+ Object actualObject = checkAdapter(obj, cls, anns, true);
+ Class<?> actualClass = obj != actualObject || cls.isInterface()
+ ? actualObject.getClass() : cls;
+ marshal(actualObject, actualClass, genericType, encoding, os, m, anns);
+ }
+ } catch (JAXBException e) {
+ handleJAXBException(e, false);
+ } catch (WebApplicationException e) {
+ throw e;
+ } catch (Exception e) {
+ LOG.warning(ExceptionUtils.getStackTrace(e));
+ throw ExceptionUtils.toInternalServerErrorException(e, null);
+ }
+ }
+
+ protected void marshalCollection(Class<?> originalCls, Object collection,
+ Type genericType, String enc, OutputStream os,
+ MediaType m, Annotation[] anns)
+ throws Exception {
+
+ Class<?> actualClass = InjectionUtils.getActualType(genericType);
+ actualClass = getActualType(actualClass, genericType, anns);
+
+ Collection<?> c = originalCls.isArray() ? Arrays.asList((Object[]) collection)
+ : (Collection<?>) collection;
+
+ Iterator<?> it = c.iterator();
+
+ Object firstObj = it.hasNext() ? it.next() : null;
+
+ final QName qname;
+ if (firstObj instanceof JAXBElement) {
+ JAXBElement<?> el = (JAXBElement<?>)firstObj;
+ qname = el.getName();
+ actualClass = el.getDeclaredType();
+ } else {
+ qname = getCollectionWrapperQName(actualClass, genericType, firstObj, true);
+ }
+ if (qname == null) {
+ String message = new org.apache.cxf.common.i18n.Message("NO_COLLECTION_ROOT",
+ BUNDLE).toString();
+ throw new WebApplicationException(Response.serverError()
+ .entity(message).build());
+ }
+
+ os.write((XML_PI_START + (enc == null ? StandardCharsets.UTF_8.name() : enc) + "\"?>").getBytes());
+
+ final String startTag;
+ final String endTag;
+ if (!qname.getNamespaceURI().isEmpty()) {
+ String prefix = nsPrefixes.get(qname.getNamespaceURI());
+ if (prefix == null) {
+ prefix = "ns1";
+ }
+ startTag = "<" + prefix + ':' + qname.getLocalPart() + " xmlns:" + prefix + "=\""
+ + qname.getNamespaceURI() + "\">";
+ endTag = "</" + prefix + ':' + qname.getLocalPart() + ">";
+ } else {
+ startTag = "<" + qname.getLocalPart() + ">";
+ endTag = "</" + qname.getLocalPart() + ">";
+ }
+ os.write(startTag.getBytes());
+ if (firstObj != null) {
+ XmlJavaTypeAdapter adapter =
+ org.apache.cxf.jaxrs.utils.JAXBUtils.getAdapter(firstObj.getClass(), anns);
+ marshalCollectionMember(JAXBUtils.useAdapter(firstObj, adapter, true),
+ actualClass, genericType, enc, os, anns, m,
+ qname.getNamespaceURI());
+ while (it.hasNext()) {
+ marshalCollectionMember(JAXBUtils.useAdapter(it.next(), adapter, true), actualClass,
+ genericType, enc, os, anns, m,
+ qname.getNamespaceURI());
+ }
+ }
+ os.write(endTag.getBytes());
+ }
+ //CHECKSTYLE:OFF
+ protected void marshalCollectionMember(Object obj,
+ Class<?> cls,
+ Type genericType,
+ String enc,
+ OutputStream os,
+ Annotation[] anns,
+ MediaType mt,
+ String ns) throws Exception {
+ //CHECKSTYLE:ON
+ if (!(obj instanceof JAXBElement)) {
+ obj = convertToJaxbElementIfNeeded(obj, cls, genericType);
+ }
+
+ if (obj instanceof JAXBElement && cls != JAXBElement.class) {
+ cls = JAXBElement.class;
+ }
+
+ Marshaller ms = createMarshaller(obj, cls, genericType, enc);
+ ms.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
+ if (ns.length() > 0) {
+ Map<String, String> map = new HashMap<>();
+ // set the default just in case
+ if (!nsPrefixes.containsKey(ns)) {
+ map.put(ns, "ns1");
+ }
+ map.putAll(nsPrefixes);
+ setNamespaceMapper(ms, map);
+ }
+ marshal(obj, cls, genericType, enc, os, anns, mt, ms);
+ }
+
+ protected void marshal(Object obj, Class<?> cls, Type genericType,
+ String enc, OutputStream os, MediaType mt) throws Exception {
+ marshal(obj, cls, genericType, enc, os, mt, new Annotation[]{});
+ }
+
+ protected void marshal(Object obj, Class<?> cls, Type genericType,
+ String enc, OutputStream os, MediaType mt,
+ Annotation[] anns) throws Exception {
+ obj = convertToJaxbElementIfNeeded(obj, cls, genericType);
+ if (obj instanceof JAXBElement && cls != JAXBElement.class) {
+ cls = JAXBElement.class;
+ }
+
+ Marshaller ms = createMarshaller(obj, cls, genericType, enc);
+ if (!nsPrefixes.isEmpty()) {
+ setNamespaceMapper(ms, nsPrefixes);
+ }
+ addAttachmentMarshaller(ms);
+ processXmlAnnotations(ms, mt, anns);
+ marshal(obj, cls, genericType, enc, os, anns, mt, ms);
+ }
+
+ private void processXmlAnnotations(Marshaller ms, MediaType mt, Annotation[] anns) throws Exception {
+ if (anns == null) {
+ return;
+ }
+ for (Annotation ann : anns) {
+ if (ann.annotationType() == XMLInstruction.class) {
+ addProcessingInstructions(ms, (XMLInstruction)ann);
+ } else if (ann.annotationType() == XSISchemaLocation.class) {
+ addSchemaLocation(ms, (XSISchemaLocation)ann);
+ } else if (ann.annotationType() == XSLTTransform.class) {
+ addXslProcessingInstruction(ms, mt, (XSLTTransform)ann);
+ }
+ }
+ }
+
+ private void addProcessingInstructions(Marshaller ms, XMLInstruction pi) throws Exception {
+ String value = pi.value();
+ int ind = value.indexOf("href='");
+ if (ind > 0) {
+ String relRef = value.substring(ind + 6);
+ relRef = relRef.substring(0, relRef.length() - 3).trim();
+ if (relRef.endsWith("'")) {
+ relRef = relRef.substring(0, relRef.length() - 1);
+ }
+ String absRef = resolveXMLResourceURI(relRef);
+ value = value.substring(0, ind + 6) + absRef + "'?>";
+ }
+ setXmlPiProperty(ms, value);
+ }
+
+ private void addXslProcessingInstruction(Marshaller ms, MediaType mt, XSLTTransform ann)
+ throws Exception {
+ if (ann.type() == XSLTTransform.TransformType.CLIENT
+ || ann.type() == XSLTTransform.TransformType.BOTH && ann.mediaTypes().length > 0) {
+ for (String s : ann.mediaTypes()) {
+ if (mt.isCompatible(JAXRSUtils.toMediaType(s))) {
+ return;
+ }
+ }
+ String absRef = resolveXMLResourceURI(ann.value());
+ String xslPi = "<?xml-stylesheet type=\"text/xsl\" href=\"" + absRef + "\"?>";
+ setXmlPiProperty(ms, xslPi);
+ }
+ }
+
+ private void addSchemaLocation(Marshaller ms, XSISchemaLocation sl) throws Exception {
+ String value = sl.resolve() ? resolveXMLResourceURI(sl.value()) : sl.value();
+ String propName = !sl.noNamespace()
+ ? Marshaller.JAXB_SCHEMA_LOCATION : Marshaller.JAXB_NO_NAMESPACE_SCHEMA_LOCATION;
+ ms.setProperty(propName, value);
+ }
+
+ protected String resolveXMLResourceURI(String path) {
+ MessageContext mc = getContext();
+ if (mc != null) {
+ String httpBasePath = (String)mc.get("http.base.path");
+ final UriBuilder builder;
+ if (httpBasePath != null) {
+ builder = UriBuilder.fromPath(httpBasePath);
+ } else {
+ builder = mc.getUriInfo().getBaseUriBuilder();
+ }
+ return builder.path(path).path(xmlResourceOffset).build().toString();
+ }
+ return path;
+ }
+
+
+
+ protected void addAttachmentMarshaller(Marshaller ms) {
+ Collection<Attachment> attachments = getAttachments(true);
+ if (attachments != null) {
+ Object value = getContext().getContextualProperty(Message.MTOM_THRESHOLD);
+ Integer threshold = value != null ? Integer.valueOf(value.toString()) : Integer.valueOf(0);
+ ms.setAttachmentMarshaller(new JAXBAttachmentMarshaller(
+ attachments, threshold));
+ }
+ }
+
+ protected void addAttachmentUnmarshaller(Unmarshaller um) {
+ Collection<Attachment> attachments = getAttachments(false);
+ if (attachments != null) {
+ um.setAttachmentUnmarshaller(new JAXBAttachmentUnmarshaller(
+ attachments));
+ }
+ }
+
+ private Collection<Attachment> getAttachments(boolean write) {
+ MessageContext mc = getContext();
+ if (mc != null) {
+ // TODO: there has to be a better fix
+ String propertyName = write ? "WRITE-" + Message.ATTACHMENTS : Message.ATTACHMENTS;
+ return CastUtils.cast((Collection<?>)mc.get(propertyName));
+ }
+ return null;
+ }
+ //CHECKSTYLE:OFF
+ protected final void marshal(Object obj, Class<?> cls, Type genericType,
+ String enc, OutputStream os,
+ Annotation[] anns, MediaType mt, Marshaller ms)
+ throws Exception {
+ //CHECKSTYLE:ON
+ for (Map.Entry<String, Object> entry : mProperties.entrySet()) {
+ ms.setProperty(entry.getKey(), entry.getValue());
+ }
+ MessageContext mc = getContext();
+ if (mc != null) {
+ // check Marshaller properties which might've been set earlier on,
+ // they'll overwrite statically configured ones
+ for (String key : MARSHALLER_PROPERTIES) {
+ Object value = mc.get(key);
+ if (value != null) {
+ ms.setProperty(key, value);
+ }
+ }
+
+ }
+ XMLStreamWriter writer = getStreamWriter(obj, os, mt);
+ if (writer != null) {
+ if (os == null) {
+ ms.setProperty(Marshaller.JAXB_FRAGMENT, true);
+ } else if (mc != null) {
+ if (mc.getContent(XMLStreamWriter.class) != null) {
+ ms.setProperty(Marshaller.JAXB_FRAGMENT, true);
+ }
+ mc.put(XMLStreamWriter.class.getName(), writer);
+ }
+ marshalToWriter(ms, obj, writer, anns, mt);
+ if (mc != null) {
+ writer.writeEndDocument();
+ }
+ } else {
+ marshalToOutputStream(ms, obj, os, anns, mt);
+ }
+ }
+
+ protected XMLStreamWriter getStreamWriter(Object obj, OutputStream os, MediaType mt) {
+ XMLStreamWriter writer = null;
+ MessageContext mc = getContext();
+ if (mc != null) {
+ writer = mc.getContent(XMLStreamWriter.class);
+ if (writer == null) {
+ XMLOutputFactory factory = (XMLOutputFactory)mc.get(XMLOutputFactory.class.getName());
+ if (factory != null) {
+ try {
+ writer = factory.createXMLStreamWriter(os);
+ } catch (XMLStreamException e) {
+ throw ExceptionUtils.toInternalServerErrorException(
+ new RuntimeException("Cant' create XMLStreamWriter", e), null);
+ }
+ }
+ }
+ if (writer == null && getEnableStreaming()) {
+ writer = StaxUtils.createXMLStreamWriter(os);
+ }
+ }
+
+ if (writer == null && os == null) {
+ writer = getStreamHandlerFromCurrentMessage(XMLStreamWriter.class);
+ }
+ return createTransformWriterIfNeeded(writer, os, true);
+ }
+
+ protected void marshalToOutputStream(Marshaller ms, Object obj, OutputStream os,
+ Annotation[] anns, MediaType mt)
+ throws Exception {
+ org.apache.cxf.common.jaxb.JAXBUtils.setMinimumEscapeHandler(ms);
+ if (os == null) {
+ Writer writer = getStreamHandlerFromCurrentMessage(Writer.class);
+ if (writer == null) {
+ LOG.severe("No OutputStream, Writer, or XMLStreamWriter is available");
+ throw ExceptionUtils.toInternalServerErrorException(null, null);
+ }
+ ms.marshal(obj, writer);
+ writer.flush();
+ } else {
+ ms.marshal(obj, os);
+ }
+ }
+
+ protected void marshalToWriter(Marshaller ms, Object obj, XMLStreamWriter writer,
+ Annotation[] anns, MediaType mt)
+ throws Exception {
+ org.apache.cxf.common.jaxb.JAXBUtils.setNoEscapeHandler(ms);
+ ms.marshal(obj, writer);
+ }
+
+ public void setXmlPiPropertyName(String xmlPiPropertyName) {
+ this.xmlPiPropertyName = xmlPiPropertyName;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/provider/MultipartProvider.java b/transform/src/patch/java/org/apache/cxf/jaxrs/provider/MultipartProvider.java
new file mode 100644
index 0000000..e129eff
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/provider/MultipartProvider.java
@@ -0,0 +1,474 @@
+/**
+ * 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.jaxrs.provider;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import javax.activation.DataHandler;
+import javax.activation.DataSource;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.cxf.attachment.AttachmentUtil;
+import org.apache.cxf.attachment.ByteDataSource;
+import org.apache.cxf.common.i18n.BundleUtils;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.PrimitiveUtils;
+import org.apache.cxf.common.util.PropertyUtils;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.jaxrs.ext.MessageContext;
+import org.apache.cxf.jaxrs.ext.multipart.Attachment;
+import org.apache.cxf.jaxrs.ext.multipart.ContentDisposition;
+import org.apache.cxf.jaxrs.ext.multipart.InputStreamDataSource;
+import org.apache.cxf.jaxrs.ext.multipart.Multipart;
+import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
+import org.apache.cxf.jaxrs.ext.multipart.MultipartOutputFilter;
+import org.apache.cxf.jaxrs.impl.MetadataMap;
+import org.apache.cxf.jaxrs.utils.AnnotationUtils;
+import org.apache.cxf.jaxrs.utils.ExceptionUtils;
+import org.apache.cxf.jaxrs.utils.InjectionUtils;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.jaxrs.utils.multipart.AttachmentUtils;
+import org.apache.cxf.message.Message;
+
+@Provider
+@Consumes({"multipart/related", "multipart/mixed", "multipart/alternative", "multipart/form-data" })
+@Produces({"multipart/related", "multipart/mixed", "multipart/alternative", "multipart/form-data" })
+public class MultipartProvider extends AbstractConfigurableProvider
+ implements MessageBodyReader<Object>, MessageBodyWriter<Object> {
+
+ private static final String SUPPORT_TYPE_AS_MULTIPART = "support.type.as.multipart";
+ private static final String SINGLE_PART_IS_COLLECTION = "single.multipart.is.collection";
+ private static final Logger LOG = LogUtils.getL7dLogger(MultipartProvider.class);
+ private static final ResourceBundle BUNDLE = BundleUtils.getBundle(MultipartProvider.class);
+ private static final Set<Class<?>> WELL_KNOWN_MULTIPART_CLASSES = new HashSet<>();
+ private static final Set<String> MULTIPART_SUBTYPES = new HashSet<>();
+ static {
+ WELL_KNOWN_MULTIPART_CLASSES.add(MultipartBody.class);
+ WELL_KNOWN_MULTIPART_CLASSES.add(Attachment.class);
+
+ MULTIPART_SUBTYPES.add("form-data");
+ MULTIPART_SUBTYPES.add("mixed");
+ MULTIPART_SUBTYPES.add("related");
+ MULTIPART_SUBTYPES.add("alternative");
+ }
+
+ @Context
+ private MessageContext mc;
+ private String attachmentDir;
+ private String attachmentThreshold;
+ private String attachmentMaxSize;
+
+ public void setMessageContext(MessageContext context) {
+ this.mc = context;
+ }
+
+ public void setAttachmentDirectory(String dir) {
+ attachmentDir = dir;
+ }
+
+ public void setAttachmentThreshold(String threshold) {
+ attachmentThreshold = threshold;
+ }
+
+ public void setAttachmentMaxSize(String maxSize) {
+ attachmentMaxSize = maxSize;
+ }
+
+ public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations,
+ MediaType mt) {
+ return isSupported(type, annotations, mt);
+
+ }
+
+ private boolean isSupported(Class<?> type, Annotation[] anns, MediaType mt) {
+ return mediaTypeSupported(mt)
+ && (WELL_KNOWN_MULTIPART_CLASSES.contains(type)
+ || Collection.class.isAssignableFrom(type)
+ || Map.class.isAssignableFrom(type) && type != MultivaluedMap.class
+ || AnnotationUtils.getAnnotation(anns, Multipart.class) != null
+ || PropertyUtils.isTrue(mc.getContextualProperty(SUPPORT_TYPE_AS_MULTIPART)));
+ }
+
+ protected void checkContentLength() {
+ if (mc != null && isPayloadEmpty(mc.getHttpHeaders())) {
+ String message = new org.apache.cxf.common.i18n.Message("EMPTY_BODY", BUNDLE).toString();
+ LOG.warning(message);
+ throw new WebApplicationException(400);
+ }
+ }
+
+ public Object readFrom(Class<Object> c, Type t, Annotation[] anns, MediaType mt,
+ MultivaluedMap<String, String> headers, InputStream is)
+ throws IOException, WebApplicationException {
+ checkContentLength();
+ List<Attachment> infos = AttachmentUtils.getAttachments(
+ mc, attachmentDir, attachmentThreshold, attachmentMaxSize);
+
+ boolean collectionExpected = Collection.class.isAssignableFrom(c);
+ if (collectionExpected
+ && AnnotationUtils.getAnnotation(anns, Multipart.class) == null) {
+ return getAttachmentCollection(t, infos, anns);
+ }
+ if (c.isAssignableFrom(Map.class)) {
+ Map<String, Object> map = new LinkedHashMap<>(infos.size());
+ Class<?> actual = getActualType(t, 1);
+ for (Attachment a : infos) {
+ map.put(a.getContentType().toString(), fromAttachment(a, actual, actual, anns));
+ }
+ return map;
+ }
+ if (MultipartBody.class.isAssignableFrom(c)) {
+ return new MultipartBody(infos);
+ }
+
+ Multipart id = AnnotationUtils.getAnnotation(anns, Multipart.class);
+ Attachment multipart = AttachmentUtils.getMultipart(id, mt, infos);
+ if (multipart != null) {
+ if (collectionExpected
+ && !mediaTypeSupported(multipart.getContentType())
+ && !PropertyUtils.isTrue(mc.getContextualProperty(SINGLE_PART_IS_COLLECTION))) {
+ List<Attachment> allMultiparts = AttachmentUtils.getMatchingAttachments(id, infos);
+ return getAttachmentCollection(t, allMultiparts, anns);
+ }
+ return fromAttachment(multipart, c, t, anns);
+ }
+
+ if (id != null && !id.required()) {
+ /*
+ * Return default value for a missing optional part
+ */
+ Object defaultValue = null;
+ if (c.isPrimitive()) {
+ defaultValue = PrimitiveUtils.read((Class<?>)c == boolean.class ? "false" : "0", c);
+ }
+ return defaultValue;
+ }
+
+ throw ExceptionUtils.toBadRequestException(null, null);
+
+ }
+
+ private Object getAttachmentCollection(Type t, List<Attachment> infos, Annotation[] anns) throws IOException {
+ Class<?> actual = getActualType(t, 0);
+ if (Attachment.class.isAssignableFrom(actual)) {
+ return infos;
+ }
+ Collection<Object> objects = new ArrayList<>();
+ for (Attachment a : infos) {
+ objects.add(fromAttachment(a, actual, actual, anns));
+ }
+ return objects;
+ }
+
+ private Class<?> getActualType(Type type, int pos) {
+ Class<?> actual = null;
+ try {
+ actual = InjectionUtils.getActualType(type, pos);
+ } catch (Exception ex) {
+ // ignore;
+ }
+ return actual != null && actual != Object.class ? actual : Attachment.class;
+ }
+
+ private <T> Object fromAttachment(Attachment multipart, Class<T> c, Type t, Annotation[] anns)
+ throws IOException {
+ if (DataHandler.class.isAssignableFrom(c)) {
+ return multipart.getDataHandler();
+ } else if (DataSource.class.isAssignableFrom(c)) {
+ return multipart.getDataHandler().getDataSource();
+ } else if (Attachment.class.isAssignableFrom(c)) {
+ return multipart;
+ } else {
+ if (mediaTypeSupported(multipart.getContentType())) {
+ mc.put("org.apache.cxf.multipart.embedded", true);
+ mc.put("org.apache.cxf.multipart.embedded.ctype", multipart.getContentType());
+ mc.put("org.apache.cxf.multipart.embedded.input",
+ multipart.getDataHandler().getInputStream());
+ anns = new Annotation[]{};
+ }
+ MessageBodyReader<T> r =
+ mc.getProviders().getMessageBodyReader(c, t, anns, multipart.getContentType());
+ if (r != null) {
+ InputStream is = multipart.getDataHandler().getInputStream();
+ return r.readFrom(c, t, anns, multipart.getContentType(), multipart.getHeaders(),
+ is);
+ }
+ }
+ return null;
+ }
+
+ private boolean mediaTypeSupported(MediaType mt) {
+ return "multipart".equals(mt.getType()) && MULTIPART_SUBTYPES.contains(mt.getSubtype());
+ }
+
+ public long getSize(Object t, Class<?> type, Type genericType, Annotation[] annotations,
+ MediaType mediaType) {
+ return -1;
+ }
+
+ public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations,
+ MediaType mt) {
+ return isSupported(type, annotations, mt);
+ }
+
+
+ public void writeTo(Object obj, Class<?> type, Type genericType, Annotation[] anns, MediaType mt,
+ MultivaluedMap<String, Object> headers, OutputStream os)
+ throws IOException, WebApplicationException {
+
+ List<Attachment> handlers = convertToDataHandlers(obj, type, genericType, anns, mt);
+ if (mc.get(AttachmentUtils.OUT_FILTERS) != null) {
+ List<MultipartOutputFilter> filters = CastUtils.cast((List<?>)mc.get(AttachmentUtils.OUT_FILTERS));
+ for (MultipartOutputFilter filter : filters) {
+ filter.filter(handlers);
+ }
+ }
+ mc.put(MultipartBody.OUTBOUND_MESSAGE_ATTACHMENTS, handlers);
+ handlers.get(0).getDataHandler().writeTo(os);
+ }
+
+ private List<Attachment> convertToDataHandlers(Object obj,
+ Class<?> type, Type genericType,
+ Annotation[] anns, MediaType mt) throws IOException {
+ if (Map.class.isAssignableFrom(obj.getClass())) {
+ Map<Object, Object> objects = CastUtils.cast((Map<?, ?>)obj);
+ List<Attachment> handlers = new ArrayList<>(objects.size());
+ int i = 0;
+ for (Iterator<Map.Entry<Object, Object>> iter = objects.entrySet().iterator();
+ iter.hasNext();) {
+ Map.Entry<Object, Object> entry = iter.next();
+ Object value = entry.getValue();
+ Attachment handler = createDataHandler(value, value.getClass(),
+ new Annotation[]{},
+ entry.getKey().toString(),
+ mt.toString(),
+ i++);
+ handlers.add(handler);
+ }
+ return handlers;
+ }
+ String rootMediaType = getRootMediaType(anns, mt);
+ if (List.class.isAssignableFrom(obj.getClass())) {
+ return getAttachments((List<?>)obj, rootMediaType);
+ }
+ if (MultipartBody.class.isAssignableFrom(type)) {
+ List<Attachment> atts = ((MultipartBody)obj).getAllAttachments();
+ // these attachments may have no DataHandlers, but objects only
+ return getAttachments(atts, rootMediaType);
+ }
+ Attachment handler = createDataHandler(obj,
+ genericType, anns,
+ rootMediaType, mt.toString(), 1);
+ return Collections.singletonList(handler);
+ }
+
+ private List<Attachment> getAttachments(List<?> objects, String rootMediaType) throws IOException {
+ List<Attachment> handlers = new ArrayList<>(objects.size());
+ for (int i = 0; i < objects.size(); i++) {
+ Object value = objects.get(i);
+ Attachment handler = createDataHandler(value,
+ value.getClass(), new Annotation[]{},
+ rootMediaType, rootMediaType, i);
+ handlers.add(handler);
+ }
+ return handlers;
+ }
+
+ private <T> Attachment createDataHandler(T obj,
+ Type genericType,
+ Annotation[] anns,
+ String mimeType,
+ String mainMediaType,
+ int id) throws IOException {
+ @SuppressWarnings("unchecked")
+ Class<T> cls = (Class<T>)obj.getClass();
+ return createDataHandler(obj, cls, genericType, anns, mimeType, mainMediaType, id);
+ }
+ private <T> Attachment createDataHandler(T obj,
+ Class<T> cls,
+ Type genericType,
+ Annotation[] anns,
+ String mimeType,
+ String mainMediaType,
+ int id) throws IOException {
+ final DataHandler dh;
+ if (InputStream.class.isAssignableFrom(obj.getClass())) {
+ dh = createInputStreamDH((InputStream)obj, mimeType);
+ } else if (DataHandler.class.isAssignableFrom(obj.getClass())) {
+ dh = (DataHandler)obj;
+ } else if (DataSource.class.isAssignableFrom(obj.getClass())) {
+ dh = new DataHandler((DataSource)obj);
+ } else if (File.class.isAssignableFrom(obj.getClass())) {
+ File f = (File)obj;
+ ContentDisposition cd = mainMediaType.startsWith(MediaType.MULTIPART_FORM_DATA)
+ ? new ContentDisposition("form-data;name=file;filename=" + f.getName()) : null;
+ return new Attachment(AttachmentUtil.BODY_ATTACHMENT_ID, Files.newInputStream(f.toPath()), cd);
+ } else if (Attachment.class.isAssignableFrom(obj.getClass())) {
+ Attachment att = (Attachment)obj;
+ if (att.getObject() == null) {
+ return att;
+ }
+ dh = getHandlerForObject(att.getObject(),
+ att.getObject().getClass(), new Annotation[]{},
+ att.getContentType().toString(), id);
+ return new Attachment(att.getContentId(), dh, att.getHeaders());
+ } else if (byte[].class.isAssignableFrom(obj.getClass())) {
+ ByteDataSource source = new ByteDataSource((byte[])obj);
+ source.setContentType(mimeType);
+ dh = new DataHandler(source);
+ } else {
+ dh = getHandlerForObject(obj, cls, genericType, anns, mimeType);
+ }
+ String contentId = getContentId(anns, id);
+ MultivaluedMap<String, String> headers = new MetadataMap<>();
+ headers.putSingle("Content-Type", mimeType);
+
+ return new Attachment(contentId, dh, headers);
+ }
+
+ private String getContentId(Annotation[] anns, int id) {
+ Multipart part = AnnotationUtils.getAnnotation(anns, Multipart.class);
+ if (part != null && !"".equals(part.value())) {
+ return part.value();
+ }
+ return id == 0 ? AttachmentUtil.BODY_ATTACHMENT_ID : Integer.toString(id);
+ }
+
+ private <T> DataHandler getHandlerForObject(T obj,
+ Class<T> cls, Type genericType,
+ Annotation[] anns,
+ String mimeType) {
+ MediaType mt = JAXRSUtils.toMediaType(mimeType);
+ mc.put(ProviderFactory.ACTIVE_JAXRS_PROVIDER_KEY, this);
+
+ final MessageBodyWriter<T> r;
+ try {
+ r = mc.getProviders().getMessageBodyWriter(cls, genericType, anns, mt);
+ } finally {
+ mc.put(ProviderFactory.ACTIVE_JAXRS_PROVIDER_KEY, null);
+ }
+ if (r == null) {
+ org.apache.cxf.common.i18n.Message message =
+ new org.apache.cxf.common.i18n.Message("NO_MSG_WRITER",
+ BUNDLE,
+ cls);
+ LOG.severe(message.toString());
+ throw ExceptionUtils.toInternalServerErrorException(null, null);
+ }
+
+ return new MessageBodyWriterDataHandler<T>(r, obj, cls, genericType, anns, mt);
+ }
+ private <T> DataHandler getHandlerForObject(T obj,
+ Type genericType,
+ Annotation[] anns,
+ String mimeType, int id) {
+ @SuppressWarnings("unchecked")
+ Class<T> cls = (Class<T>)obj.getClass();
+ return getHandlerForObject(obj, cls, genericType, anns, mimeType);
+ }
+
+ private DataHandler createInputStreamDH(InputStream is, String mimeType) {
+ return new DataHandler(new InputStreamDataSource(is, mimeType));
+ }
+
+ private String getRootMediaType(Annotation[] anns, MediaType mt) {
+ String mimeType = mt.getParameters().get("type");
+ if (mimeType != null) {
+ return mimeType;
+ }
+ Multipart id = AnnotationUtils.getAnnotation(anns, Multipart.class);
+ if (id != null && !MediaType.WILDCARD.equals(id.type())) {
+ mimeType = id.type();
+ }
+ if (mimeType == null) {
+ if (PropertyUtils.isTrue(mc.getContextualProperty(Message.MTOM_ENABLED))) {
+ mimeType = "text/xml";
+ } else {
+ mimeType = MediaType.APPLICATION_OCTET_STREAM;
+ }
+ }
+ return mimeType;
+ }
+
+ private static class MessageBodyWriterDataHandler<T> extends DataHandler {
+ private MessageBodyWriter<T> writer;
+ private T obj;
+ private Class<T> cls;
+ private Type genericType;
+ private Annotation[] anns;
+ private MediaType contentType;
+ MessageBodyWriterDataHandler(MessageBodyWriter<T> writer,
+ T obj,
+ Class<T> cls,
+ Type genericType,
+ Annotation[] anns,
+ MediaType contentType) {
+ super(new ByteDataSource("1".getBytes(), contentType.toString()));
+ this.writer = writer;
+ this.obj = obj;
+ this.cls = cls;
+ this.genericType = genericType;
+ this.anns = anns;
+ this.contentType = contentType;
+ }
+
+ @Override
+ public void writeTo(OutputStream os) {
+ try {
+ writer.writeTo(obj, cls, genericType, anns, contentType,
+ new MetadataMap<String, Object>(), os);
+ } catch (IOException ex) {
+ throw ExceptionUtils.toInternalServerErrorException(ex, null);
+ }
+ }
+
+ @Override
+ public String getContentType() {
+ return contentType.toString();
+ }
+
+ // TODO : throw UnsupportedOperationException for all other DataHandler methods
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/provider/SourceProvider.java b/transform/src/patch/java/org/apache/cxf/jaxrs/provider/SourceProvider.java
new file mode 100644
index 0000000..76980b8
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/provider/SourceProvider.java
@@ -0,0 +1,236 @@
+/**
+ * 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.jaxrs.provider;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.util.logging.Logger;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.Source;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.sax.SAXSource;
+import javax.xml.transform.stream.StreamSource;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.io.CachedOutputStream;
+import org.apache.cxf.jaxrs.ext.MessageContext;
+import org.apache.cxf.jaxrs.ext.xml.XMLSource;
+import org.apache.cxf.jaxrs.utils.ExceptionUtils;
+import org.apache.cxf.jaxrs.utils.HttpUtils;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.PhaseInterceptorChain;
+import org.apache.cxf.staxutils.DepthExceededStaxException;
+import org.apache.cxf.staxutils.StaxSource;
+import org.apache.cxf.staxutils.StaxUtils;
+
+@Provider
+@Produces({"application/xml", "application/*+xml", "text/xml" })
+@Consumes({"application/xml", "application/*+xml", "text/xml", "text/html" })
+public class SourceProvider<T> extends AbstractConfigurableProvider implements
+ MessageBodyReader<T>, MessageBodyWriter<T> {
+
+ private static final String PREFERRED_FORMAT = "source-preferred-format";
+ private static final Logger LOG = LogUtils.getL7dLogger(SourceProvider.class);
+ @Context
+ private MessageContext context;
+
+
+ public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mt) {
+ return Source.class.isAssignableFrom(type)
+ || Node.class.isAssignableFrom(type);
+ }
+
+ public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mt) {
+ return Source.class.isAssignableFrom(type)
+ || XMLSource.class.isAssignableFrom(type)
+ || Document.class.isAssignableFrom(type);
+ }
+
+ public T readFrom(Class<T> source, Type genericType, Annotation[] annotations, MediaType m,
+ MultivaluedMap<String, String> headers, InputStream is)
+ throws IOException {
+
+ Class<?> theSource = source;
+ if (theSource == Source.class) {
+ String s = getPreferredSource();
+ if ("sax".equalsIgnoreCase(s) || "cxf.stax".equalsIgnoreCase(s)) {
+ theSource = SAXSource.class;
+ }
+ }
+ try {
+ if (DOMSource.class.isAssignableFrom(theSource) || Document.class.isAssignableFrom(theSource)) {
+
+ boolean docRequired = Document.class.isAssignableFrom(theSource);
+ XMLStreamReader reader = getReader(is);
+ try {
+ Document doc = StaxUtils.read(reader);
+ return source.cast(docRequired ? doc : new DOMSource(doc));
+ } catch (DepthExceededStaxException e) {
+ throw ExceptionUtils.toWebApplicationException(null, JAXRSUtils.toResponse(413));
+ } catch (XMLStreamException e) {
+ if (e.getMessage() != null && e.getMessage().startsWith("Maximum Number")) {
+ throw ExceptionUtils.toWebApplicationException(null, JAXRSUtils.toResponse(413));
+ }
+ throw ExceptionUtils.toBadRequestException(e, null);
+ } catch (Exception e) {
+ IOException ioex = new IOException("Problem creating a Source object");
+ ioex.setStackTrace(e.getStackTrace());
+ throw ioex;
+ } finally {
+ try {
+ reader.close();
+ } catch (XMLStreamException e) {
+ //ignore
+ }
+ }
+ } else if (SAXSource.class.isAssignableFrom(theSource)
+ || StaxSource.class.isAssignableFrom(theSource)) {
+ return source.cast(new StaxSource(getReader(is)));
+ } else if (StreamSource.class.isAssignableFrom(theSource)
+ || Source.class.isAssignableFrom(theSource)) {
+ return source.cast(new StreamSource(getRealStream(is)));
+ } else if (XMLSource.class.isAssignableFrom(theSource)) {
+ return source.cast(new XMLSource(getRealStream(is)));
+ }
+ } catch (ClassCastException e) {
+ String msg = "Unsupported class: " + source.getName();
+ LOG.warning(msg);
+ throw ExceptionUtils.toInternalServerErrorException(null, null);
+ }
+
+ throw new IOException("Unrecognized source");
+ }
+
+ protected XMLStreamReader getReader(InputStream is) {
+ XMLStreamReader reader = getReaderFromMessage();
+ if (reader == null) {
+ reader = StaxUtils.createXMLStreamReader(is);
+ }
+ return configureReaderRestrictions(reader);
+ }
+
+ protected XMLStreamReader configureReaderRestrictions(XMLStreamReader reader) {
+ Message message = PhaseInterceptorChain.getCurrentMessage();
+ if (message != null) {
+ try {
+ return StaxUtils.configureReader(reader, message);
+ } catch (XMLStreamException ex) {
+ throw ExceptionUtils.toInternalServerErrorException(ex, null);
+ }
+ }
+ return reader;
+ }
+
+ protected InputStream getRealStream(InputStream is) throws IOException {
+ XMLStreamReader reader = getReaderFromMessage();
+ return reader == null ? is : getStreamFromReader(reader);
+ }
+
+ private InputStream getStreamFromReader(XMLStreamReader input)
+ throws IOException {
+
+ try (CachedOutputStream out = new CachedOutputStream()) {
+ StaxUtils.copy(input, out);
+ return out.getInputStream();
+ } catch (XMLStreamException ex) {
+ throw new IOException("XMLStreamException:" + ex.getMessage());
+ }
+ }
+
+ protected XMLStreamReader getReaderFromMessage() {
+ MessageContext mc = getContext();
+ if (mc != null) {
+ return mc.getContent(XMLStreamReader.class);
+ }
+ return null;
+ }
+
+ public void writeTo(T source, Class<?> clazz, Type genericType, Annotation[] annotations,
+ MediaType mt, MultivaluedMap<String, Object> headers, OutputStream os)
+ throws IOException {
+
+ String encoding = HttpUtils.getSetEncoding(mt, headers, StandardCharsets.UTF_8.name());
+
+ final XMLStreamReader reader;
+ if (source instanceof Source) {
+ reader = StaxUtils.createXMLStreamReader((Source)source);
+ } else if (source instanceof Document) {
+ reader = StaxUtils.createXMLStreamReader((Document)source);
+ } else {
+ reader = StaxUtils.createXMLStreamReader(new DOMSource((Node)source));
+ }
+ XMLStreamWriter writer = StaxUtils.createXMLStreamWriter(os, encoding);
+ try {
+ StaxUtils.copy(reader, writer);
+ } catch (XMLStreamException e) {
+ throw ExceptionUtils.toInternalServerErrorException(e, null);
+ } finally {
+ try {
+ reader.close();
+ } catch (XMLStreamException e) {
+ //ignore
+ }
+ try {
+ writer.flush();
+ writer.close();
+ } catch (XMLStreamException e) {
+ //ignore
+ }
+ }
+ }
+
+ public long getSize(T source, Class<?> type, Type genericType, Annotation[] annotations,
+ MediaType mt) {
+ return -1;
+ }
+
+ protected String getPreferredSource() {
+ MessageContext mc = getContext();
+ String source = null;
+ if (mc != null) {
+ source = (String)mc.getContextualProperty(PREFERRED_FORMAT);
+ }
+ return source != null ? source : "sax";
+
+ }
+
+ protected MessageContext getContext() {
+ return context;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/provider/XSLTJaxbProvider.java b/transform/src/patch/java/org/apache/cxf/jaxrs/provider/XSLTJaxbProvider.java
new file mode 100644
index 0000000..4250963
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/provider/XSLTJaxbProvider.java
@@ -0,0 +1,588 @@
+/**
+ * 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.jaxrs.provider;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.PathSegment;
+import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.ext.Provider;
+import javax.xml.XMLConstants;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Templates;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.sax.SAXSource;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.io.CachedOutputStream;
+import org.apache.cxf.jaxrs.ext.MessageContext;
+import org.apache.cxf.jaxrs.ext.xml.XSLTTransform;
+import org.apache.cxf.jaxrs.utils.AnnotationUtils;
+import org.apache.cxf.jaxrs.utils.ExceptionUtils;
+import org.apache.cxf.jaxrs.utils.InjectionUtils;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.jaxrs.utils.ResourceUtils;
+import org.apache.cxf.staxutils.StaxSource;
+import org.apache.cxf.staxutils.StaxUtils;
+
+@Produces({"application/xml", "application/*+xml", "text/xml", "text/html" })
+@Consumes({"application/xml", "application/*+xml", "text/xml", "text/html" })
+@Provider
+public class XSLTJaxbProvider<T> extends JAXBElementProvider<T> {
+
+ private static final Logger LOG = LogUtils.getL7dLogger(XSLTJaxbProvider.class);
+
+ private static final String ABSOLUTE_PATH_PARAMETER = "absolute.path";
+ private static final String BASE_PATH_PARAMETER = "base.path";
+ private static final String RELATIVE_PATH_PARAMETER = "relative.path";
+ private static final String XSLT_TEMPLATE_PROPERTY = "xslt.template";
+ private SAXTransformerFactory factory;
+ private Templates inTemplates;
+ private Templates outTemplates;
+ private Map<String, Templates> inMediaTemplates;
+ private Map<String, Templates> outMediaTemplates;
+ private ConcurrentHashMap<String, Templates> annotationTemplates =
+ new ConcurrentHashMap<>();
+
+ private List<String> inClassesToHandle;
+ private List<String> outClassesToHandle;
+ private Map<String, Object> inParamsMap;
+ private Map<String, Object> outParamsMap;
+ private Map<String, String> inProperties;
+ private Map<String, String> outProperties;
+ private URIResolver uriResolver;
+ private String systemId;
+
+ private boolean supportJaxbOnly;
+ private boolean refreshTemplates;
+ private boolean secureProcessing = true;
+
+ public void setSupportJaxbOnly(boolean support) {
+ this.supportJaxbOnly = support;
+ }
+
+ @Override
+ public boolean isReadable(Class<?> type, Type genericType, Annotation[] anns, MediaType mt) {
+ if (!super.isReadable(type, genericType, anns, mt)) {
+ return false;
+ }
+
+ if (InjectionUtils.isSupportedCollectionOrArray(type)) {
+ return supportJaxbOnly;
+ }
+
+ // if the user has set the list of in classes and a given class
+ // is in that list then it can only be handled by the template
+ if (inClassCanBeHandled(type.getName()) || inClassesToHandle == null && !supportJaxbOnly) {
+ return inTemplatesAvailable(type, anns, mt);
+ }
+ return supportJaxbOnly;
+ }
+
+ @Override
+ public boolean isWriteable(Class<?> type, Type genericType, Annotation[] anns, MediaType mt) {
+ // JAXB support is required
+ if (!super.isWriteable(type, genericType, anns, mt)) {
+ return false;
+ }
+ if (InjectionUtils.isSupportedCollectionOrArray(type)) {
+ return supportJaxbOnly;
+ }
+
+ // if the user has set the list of out classes and a given class
+ // is in that list then it can only be handled by the template
+ if (outClassCanBeHandled(type.getName()) || outClassesToHandle == null && !supportJaxbOnly) {
+ return outTemplatesAvailable(type, anns, mt);
+ }
+ return supportJaxbOnly;
+ }
+
+ protected boolean inTemplatesAvailable(Class<?> cls, Annotation[] anns, MediaType mt) {
+ return inTemplates != null
+ || inMediaTemplates != null && inMediaTemplates.containsKey(mt.getType() + "/"
+ + mt.getSubtype())
+ || getTemplatesFromAnnotation(cls, anns, mt) != null;
+ }
+
+ protected boolean outTemplatesAvailable(Class<?> cls, Annotation[] anns, MediaType mt) {
+ return outTemplates != null
+ || outMediaTemplates != null && outMediaTemplates.containsKey(mt.getType()
+ + "/" + mt.getSubtype())
+ || getTemplatesFromAnnotation(cls, anns, mt) != null;
+ }
+
+ protected Templates getTemplatesFromAnnotation(Class<?> cls,
+ Annotation[] anns,
+ MediaType mt) {
+ Templates t = null;
+ XSLTTransform ann = getXsltTransformAnn(anns, mt);
+ if (ann != null) {
+ t = annotationTemplates.get(ann.value());
+ if (t == null || refreshTemplates) {
+ String path = ann.value();
+ final String cp = "classpath:";
+ if (!path.startsWith(cp)) {
+ path = cp + path;
+ }
+ t = createTemplates(path);
+ if (t == null) {
+ createTemplates(ClassLoaderUtils.getResource(ann.value(), cls));
+ }
+ if (t != null) {
+ annotationTemplates.put(ann.value(), t);
+ }
+ }
+ }
+ return t;
+
+ }
+
+ protected Templates getAnnotationTemplates(Annotation[] anns) {
+ Templates t = null;
+ XSLTTransform ann = AnnotationUtils.getAnnotation(anns, XSLTTransform.class);
+ if (ann != null) {
+ t = annotationTemplates.get(ann.value());
+ }
+ return t;
+
+ }
+
+ protected XSLTTransform getXsltTransformAnn(Annotation[] anns, MediaType mt) {
+ XSLTTransform ann = AnnotationUtils.getAnnotation(anns, XSLTTransform.class);
+ if (ann != null && ann.type() != XSLTTransform.TransformType.CLIENT) {
+ if (ann.mediaTypes().length > 0) {
+ for (String s : ann.mediaTypes()) {
+ if (mt.isCompatible(JAXRSUtils.toMediaType(s))) {
+ return ann;
+ }
+ }
+ return null;
+ }
+ return ann;
+ }
+ return null;
+ }
+
+
+
+ protected Templates getInTemplates(Annotation[] anns, MediaType mt) {
+ Templates t = createTemplatesFromContext();
+ if (t != null) {
+ return t;
+ }
+ t = inTemplates != null ? inTemplates
+ : inMediaTemplates != null ? inMediaTemplates.get(mt.getType() + "/" + mt.getSubtype()) : null;
+ if (t == null) {
+ t = getAnnotationTemplates(anns);
+ }
+ return t;
+ }
+
+ protected Templates getOutTemplates(Annotation[] anns, MediaType mt) {
+ Templates t = createTemplatesFromContext();
+ if (t != null) {
+ return t;
+ }
+ t = outTemplates != null ? outTemplates
+ : outMediaTemplates != null ? outMediaTemplates.get(mt.getType() + "/" + mt.getSubtype()) : null;
+ if (t == null) {
+ t = getAnnotationTemplates(anns);
+ }
+ return t;
+ }
+
+ @Override
+ protected Object unmarshalFromInputStream(Unmarshaller unmarshaller, InputStream is,
+ Annotation[] anns, MediaType mt)
+ throws JAXBException {
+ try {
+
+ Templates t = createTemplates(getInTemplates(anns, mt), inParamsMap, inProperties);
+ if (t == null && supportJaxbOnly) {
+ return super.unmarshalFromInputStream(unmarshaller, is, anns, mt);
+ }
+
+ if (unmarshaller.getClass().getName().contains("eclipse")) {
+ //eclipse MOXy doesn't work properly with the XMLFilter/Reader thing
+ //so we need to bounce through a DOM
+ Source reader = new StaxSource(StaxUtils.createXMLStreamReader(is));
+ DOMResult dom = new DOMResult();
+ t.newTransformer().transform(reader, dom);
+ return unmarshaller.unmarshal(dom.getNode());
+ }
+ XMLFilter filter;
+ try {
+ filter = factory.newXMLFilter(t);
+ } catch (TransformerConfigurationException ex) {
+ TemplatesImpl ti = (TemplatesImpl)t;
+ filter = factory.newXMLFilter(ti.getTemplates());
+ trySettingProperties(filter, ti);
+ }
+ XMLReader reader = new StaxSource(StaxUtils.createXMLStreamReader(is));
+ filter.setParent(reader);
+ SAXSource source = new SAXSource();
+ source.setXMLReader(filter);
+ if (systemId != null) {
+ source.setSystemId(systemId);
+ }
+ return unmarshaller.unmarshal(source);
+ } catch (TransformerException ex) {
+ LOG.warning("Transformation exception : " + ex.getMessage());
+ throw ExceptionUtils.toInternalServerErrorException(ex, null);
+ }
+ }
+
+ private void trySettingProperties(Object filter, TemplatesImpl ti) {
+ try {
+ //Saxon doesn't allow creating a Filter or Handler from anything other than it's original
+ //Templates. That then requires setting the parameters after the fact, but there
+ //isn't a standard API for that, so we have to grab the Transformer via reflection to
+ //set the parameters.
+ Transformer tr = (Transformer)filter.getClass().getMethod("getTransformer").invoke(filter);
+ tr.setURIResolver(ti.resolver);
+ for (Map.Entry<String, Object> entry : ti.transformParameters.entrySet()) {
+ tr.setParameter(entry.getKey(), entry.getValue());
+ }
+ for (Map.Entry<String, String> entry : ti.outProps.entrySet()) {
+ tr.setOutputProperty(entry.getKey(), entry.getValue());
+ }
+ } catch (Exception e) {
+ LOG.log(Level.WARNING, "Could not set properties for transfomer", e);
+ }
+ }
+
+ @Override
+ protected Object unmarshalFromReader(Unmarshaller unmarshaller, XMLStreamReader reader,
+ Annotation[] anns, MediaType mt)
+ throws JAXBException {
+ CachedOutputStream out = new CachedOutputStream();
+ try {
+ XMLStreamWriter writer = StaxUtils.createXMLStreamWriter(out);
+ StaxUtils.copy(new StaxSource(reader), writer);
+ writer.writeEndDocument();
+ writer.flush();
+ writer.close();
+ return unmarshalFromInputStream(unmarshaller, out.getInputStream(), anns, mt);
+ } catch (Exception ex) {
+ throw ExceptionUtils.toBadRequestException(ex, null);
+ }
+ }
+
+ @Override
+ protected void marshalToWriter(Marshaller ms, Object obj, XMLStreamWriter writer,
+ Annotation[] anns, MediaType mt)
+ throws Exception {
+ CachedOutputStream out = new CachedOutputStream();
+ marshalToOutputStream(ms, obj, out, anns, mt);
+
+ StaxUtils.copy(new StreamSource(out.getInputStream()), writer);
+ }
+
+ @Override
+ protected void addAttachmentMarshaller(Marshaller ms) {
+ // complete
+ }
+
+ protected Result getStreamResult(OutputStream os, Annotation[] anns, MediaType mt) throws Exception {
+ return new StreamResult(os);
+ }
+
+ @Override
+ protected void marshalToOutputStream(Marshaller ms, Object obj, OutputStream os,
+ Annotation[] anns, MediaType mt)
+ throws Exception {
+
+ Templates t = createTemplates(getOutTemplates(anns, mt), outParamsMap, outProperties);
+ if (t == null && supportJaxbOnly) {
+ super.marshalToOutputStream(ms, obj, os, anns, mt);
+ return;
+ }
+ org.apache.cxf.common.jaxb.JAXBUtils.setMinimumEscapeHandler(ms);
+ TransformerHandler th;
+ try {
+ th = factory.newTransformerHandler(t);
+ } catch (TransformerConfigurationException ex) {
+ TemplatesImpl ti = (TemplatesImpl)t;
+ th = factory.newTransformerHandler(ti.getTemplates());
+ this.trySettingProperties(th, ti);
+ }
+ Result result = getStreamResult(os, anns, mt);
+ if (systemId != null) {
+ result.setSystemId(systemId);
+ }
+ th.setResult(result);
+
+ if (getContext() == null) {
+ th.startDocument();
+ }
+ ms.marshal(obj, th);
+ if (getContext() == null) {
+ th.endDocument();
+ }
+ }
+
+ public void setOutTemplate(String loc) {
+ outTemplates = createTemplates(loc);
+ }
+
+ public void setInTemplate(String loc) {
+ inTemplates = createTemplates(loc);
+ }
+
+ public void setInMediaTemplates(Map<String, String> map) {
+ inMediaTemplates = new HashMap<>();
+ for (Map.Entry<String, String> entry : map.entrySet()) {
+ inMediaTemplates.put(entry.getKey(), createTemplates(entry.getValue()));
+ }
+ }
+
+ public void setOutMediaTemplates(Map<String, String> map) {
+ outMediaTemplates = new HashMap<>();
+ for (Map.Entry<String, String> entry : map.entrySet()) {
+ outMediaTemplates.put(entry.getKey(), createTemplates(entry.getValue()));
+ }
+ }
+
+ public void setResolver(URIResolver resolver) {
+ uriResolver = resolver;
+ if (factory != null) {
+ factory.setURIResolver(uriResolver);
+ }
+ }
+
+ public void setSystemId(String system) {
+ systemId = system;
+ }
+
+ public void setInParameters(Map<String, Object> inParams) {
+ this.inParamsMap = inParams;
+ }
+
+ public void setOutParameters(Map<String, Object> outParams) {
+ this.outParamsMap = outParams;
+ }
+
+ public void setInProperties(Map<String, String> inProps) {
+ this.inProperties = inProps;
+ }
+
+ public void setOutProperties(Map<String, String> outProps) {
+ this.outProperties = outProps;
+ }
+
+ public void setInClassNames(List<String> classNames) {
+ inClassesToHandle = classNames;
+ }
+
+ public boolean inClassCanBeHandled(String className) {
+ return inClassesToHandle != null && inClassesToHandle.contains(className);
+ }
+
+ public void setOutClassNames(List<String> classNames) {
+ outClassesToHandle = classNames;
+ }
+
+ public boolean outClassCanBeHandled(String className) {
+ return outClassesToHandle != null && outClassesToHandle.contains(className);
+ }
+
+ protected Templates createTemplates(Templates templates,
+ Map<String, Object> configuredParams,
+ Map<String, String> outProps) {
+ if (templates == null) {
+ if (supportJaxbOnly) {
+ return null;
+ }
+ LOG.severe("No template is available");
+ throw ExceptionUtils.toInternalServerErrorException(null, null);
+ }
+
+ TemplatesImpl templ = new TemplatesImpl(templates, uriResolver);
+ MessageContext mc = getContext();
+ if (mc != null) {
+ UriInfo ui = mc.getUriInfo();
+ MultivaluedMap<String, String> params = ui.getPathParameters();
+ for (Map.Entry<String, List<String>> entry : params.entrySet()) {
+ String value = entry.getValue().get(0);
+ int ind = value.indexOf(';');
+ if (ind > 0) {
+ value = value.substring(0, ind);
+ }
+ templ.setTransformerParameter(entry.getKey(), value);
+ }
+
+ List<PathSegment> segments = ui.getPathSegments();
+ if (!segments.isEmpty()) {
+ setTransformParameters(templ, segments.get(segments.size() - 1).getMatrixParameters());
+ }
+ setTransformParameters(templ, ui.getQueryParameters());
+ templ.setTransformerParameter(ABSOLUTE_PATH_PARAMETER, ui.getAbsolutePath().toString());
+ templ.setTransformerParameter(RELATIVE_PATH_PARAMETER, ui.getPath());
+ templ.setTransformerParameter(BASE_PATH_PARAMETER, ui.getBaseUri().toString());
+ if (configuredParams != null) {
+ for (Map.Entry<String, Object> entry : configuredParams.entrySet()) {
+ templ.setTransformerParameter(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+ if (outProps != null) {
+ templ.setOutProperties(outProps);
+ }
+
+ return templ;
+ }
+
+ private void setTransformParameters(TemplatesImpl templ, MultivaluedMap<String, String> params) {
+ for (Map.Entry<String, List<String>> entry : params.entrySet()) {
+ templ.setTransformerParameter(entry.getKey(), entry.getValue().get(0));
+ }
+ }
+
+ protected Templates createTemplates(String loc) {
+ try {
+ return createTemplates(ResourceUtils.getResourceURL(loc, this.getBus()));
+ } catch (Exception ex) {
+ LOG.warning("No template can be created : " + ex.getMessage());
+ }
+ return null;
+ }
+
+ protected Templates createTemplatesFromContext() {
+ MessageContext mc = getContext();
+ if (mc != null) {
+ String template = (String)mc.getContextualProperty(XSLT_TEMPLATE_PROPERTY);
+ if (template != null) {
+ return createTemplates(template);
+ }
+ }
+ return null;
+ }
+
+ protected Templates createTemplates(URL urlStream) {
+ if (urlStream == null) {
+ return null;
+ }
+
+ try (Reader r = new BufferedReader(
+ new InputStreamReader(urlStream.openStream(), StandardCharsets.UTF_8))) {
+ Source source = new StreamSource(r);
+ source.setSystemId(urlStream.toExternalForm());
+ if (factory == null) {
+ factory = (SAXTransformerFactory)TransformerFactory.newInstance();
+ factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, secureProcessing);
+ if (uriResolver != null) {
+ factory.setURIResolver(uriResolver);
+ }
+ }
+ return factory.newTemplates(source);
+
+ } catch (Exception ex) {
+ LOG.warning("No template can be created : " + ex.getMessage());
+ }
+ return null;
+ }
+
+ public void setRefreshTemplates(boolean refresh) {
+ this.refreshTemplates = refresh;
+ }
+
+ public void setSecureProcessing(boolean secureProcessing) {
+ this.secureProcessing = secureProcessing;
+ }
+
+ private static class TemplatesImpl implements Templates {
+
+ private Templates templates;
+ private URIResolver resolver;
+ private Map<String, Object> transformParameters = new HashMap<>();
+ private Map<String, String> outProps = new HashMap<>();
+
+ TemplatesImpl(Templates templates, URIResolver resolver) {
+ this.templates = templates;
+ this.resolver = resolver;
+ }
+
+ public Templates getTemplates() {
+ return templates;
+ }
+
+ public void setTransformerParameter(String name, Object value) {
+ transformParameters.put(name, value);
+ }
+
+ public void setOutProperties(Map<String, String> props) {
+ this.outProps = props;
+ }
+
+ public Properties getOutputProperties() {
+ return templates.getOutputProperties();
+ }
+
+ public Transformer newTransformer() throws TransformerConfigurationException {
+ Transformer tr = templates.newTransformer();
+ tr.setURIResolver(resolver);
+ for (Map.Entry<String, Object> entry : transformParameters.entrySet()) {
+ tr.setParameter(entry.getKey(), entry.getValue());
+ }
+ for (Map.Entry<String, String> entry : outProps.entrySet()) {
+ tr.setOutputProperty(entry.getKey(), entry.getValue());
+ }
+ return tr;
+ }
+
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/security/JAASAuthenticationFilter.java b/transform/src/patch/java/org/apache/cxf/jaxrs/security/JAASAuthenticationFilter.java
new file mode 100644
index 0000000..4dd05e3
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/security/JAASAuthenticationFilter.java
@@ -0,0 +1,170 @@
+/**
+ * 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.jaxrs.security;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.annotation.Priority;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.Configuration;
+import javax.ws.rs.Priorities;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.PreMatching;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.cxf.interceptor.security.JAASLoginInterceptor;
+import org.apache.cxf.interceptor.security.NamePasswordCallbackHandler;
+import org.apache.cxf.jaxrs.impl.HttpHeadersImpl;
+import org.apache.cxf.jaxrs.utils.HttpUtils;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.message.Message;
+
+@PreMatching
+@Priority(Priorities.AUTHENTICATION)
+public class JAASAuthenticationFilter implements ContainerRequestFilter {
+
+ private static final List<MediaType> HTML_MEDIA_TYPES =
+ Arrays.asList(MediaType.APPLICATION_XHTML_XML_TYPE, MediaType.TEXT_HTML_TYPE);
+
+ private URI redirectURI;
+ private String realmName;
+ private boolean ignoreBasePath = true;
+
+ private JAASLoginInterceptor interceptor;
+
+ public JAASAuthenticationFilter() {
+ interceptor = new JAASLoginInterceptor() {
+ protected CallbackHandler getCallbackHandler(String name, String password) {
+ return JAASAuthenticationFilter.this.getCallbackHandler(name, password);
+ }
+ };
+ interceptor.setUseDoAs(false);
+ }
+
+ @Deprecated
+ public void setRolePrefix(String name) {
+ interceptor.setRolePrefix(name);
+ }
+
+
+ public void setIgnoreBasePath(boolean ignore) {
+ this.ignoreBasePath = ignore;
+ }
+
+ public void setContextName(String name) {
+ interceptor.setContextName(name);
+ }
+
+ public void setLoginConfig(Configuration config) {
+ interceptor.setLoginConfig(config);
+ }
+
+ public void setRoleClassifier(String rc) {
+ interceptor.setRoleClassifier(rc);
+ }
+
+ public void setRoleClassifierType(String rct) {
+ interceptor.setRoleClassifierType(rct);
+ }
+
+
+ public void setRedirectURI(String uri) {
+ this.redirectURI = URI.create(uri);
+ }
+
+ public void setRealmName(String name) {
+ this.realmName = name;
+ }
+
+ protected CallbackHandler getCallbackHandler(String name, String password) {
+ return new NamePasswordCallbackHandler(name, password);
+ }
+
+ @Override
+ public void filter(ContainerRequestContext context) {
+ Message m = JAXRSUtils.getCurrentMessage();
+ try {
+ interceptor.handleMessage(m);
+ } catch (SecurityException ex) {
+ context.abortWith(handleAuthenticationException(ex, m));
+ }
+ }
+
+ protected Response handleAuthenticationException(SecurityException ex, Message m) {
+ HttpHeaders headers = new HttpHeadersImpl(m);
+ if (redirectURI != null && isRedirectPossible(headers)) {
+
+ final URI finalRedirectURI;
+
+ if (!redirectURI.isAbsolute()) {
+ String endpointAddress = HttpUtils.getEndpointAddress(m);
+ Object basePathProperty = m.get(Message.BASE_PATH);
+ if (ignoreBasePath && basePathProperty != null && !"/".equals(basePathProperty)) {
+ int index = endpointAddress.lastIndexOf(basePathProperty.toString());
+ if (index != -1) {
+ endpointAddress = endpointAddress.substring(0, index);
+ }
+ }
+ finalRedirectURI = UriBuilder.fromUri(endpointAddress).path(redirectURI.toString()).build();
+ } else {
+ finalRedirectURI = redirectURI;
+ }
+
+ return Response.status(getRedirectStatus()).
+ header(HttpHeaders.LOCATION, finalRedirectURI).build();
+ }
+ ResponseBuilder builder = Response.status(Response.Status.UNAUTHORIZED);
+
+ StringBuilder sb = new StringBuilder();
+
+ List<String> authHeader = headers.getRequestHeader(HttpHeaders.AUTHORIZATION);
+ if (authHeader != null && !authHeader.isEmpty()) {
+ // should HttpHeadersImpl do it ?
+ String[] authValues = authHeader.get(0).split(" ");
+ if (authValues.length > 0) {
+ sb.append(authValues[0]);
+ }
+ } else {
+ sb.append("Basic");
+ }
+ if (realmName != null) {
+ sb.append(" realm=\"").append(realmName).append('"');
+ }
+ builder.header(HttpHeaders.WWW_AUTHENTICATE, sb.toString());
+
+ return builder.build();
+ }
+
+ protected Response.Status getRedirectStatus() {
+ return Response.Status.TEMPORARY_REDIRECT;
+ }
+
+ protected boolean isRedirectPossible(HttpHeaders headers) {
+ List<MediaType> clientTypes = headers.getAcceptableMediaTypes();
+ return !JAXRSUtils.intersectMimeTypes(clientTypes, HTML_MEDIA_TYPES, false)
+ .isEmpty();
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/security/KerberosAuthenticationFilter.java b/transform/src/patch/java/org/apache/cxf/jaxrs/security/KerberosAuthenticationFilter.java
new file mode 100644
index 0000000..1c7a164
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/security/KerberosAuthenticationFilter.java
@@ -0,0 +1,251 @@
+/**
+ * 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.jaxrs.security;
+
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.List;
+import java.util.logging.Logger;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.PreMatching;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.security.SimplePrincipal;
+import org.apache.cxf.common.security.SimpleSecurityContext;
+import org.apache.cxf.common.util.Base64Exception;
+import org.apache.cxf.common.util.Base64Utility;
+import org.apache.cxf.common.util.PropertyUtils;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.jaxrs.ext.MessageContext;
+import org.apache.cxf.jaxrs.utils.ExceptionUtils;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.security.SecurityContext;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+@PreMatching
+public class KerberosAuthenticationFilter implements ContainerRequestFilter {
+
+ private static final Logger LOG = LogUtils.getL7dLogger(KerberosAuthenticationFilter.class);
+
+ private static final String NEGOTIATE_SCHEME = "Negotiate";
+ private static final String PROPERTY_USE_KERBEROS_OID = "auth.spnego.useKerberosOid";
+ private static final String KERBEROS_OID = "1.2.840.113554.1.2.2";
+ private static final String SPNEGO_OID = "1.3.6.1.5.5.2";
+
+ private MessageContext messageContext;
+ private CallbackHandler callbackHandler;
+ private Configuration loginConfig;
+ private String loginContextName = "";
+ private String servicePrincipalName;
+ private String realm;
+
+ @Override
+ public void filter(ContainerRequestContext context) {
+
+ List<String> authHeaders = messageContext.getHttpHeaders()
+ .getRequestHeader(HttpHeaders.AUTHORIZATION);
+ if (authHeaders == null || authHeaders.size() != 1) {
+ LOG.fine("No Authorization header is available");
+ throw ExceptionUtils.toNotAuthorizedException(null, getFaultResponse());
+ }
+ String[] authPair = authHeaders.get(0).split(" ");
+ if (authPair.length != 2 || !NEGOTIATE_SCHEME.equalsIgnoreCase(authPair[0])) {
+ LOG.fine("Negotiate Authorization scheme is expected");
+ throw ExceptionUtils.toNotAuthorizedException(null, getFaultResponse());
+ }
+
+ byte[] serviceTicket = getServiceTicket(authPair[1]);
+
+ try {
+ Subject serviceSubject = loginAndGetSubject();
+
+ GSSContext gssContext = createGSSContext();
+
+ Subject.doAs(serviceSubject, new ValidateServiceTicketAction(gssContext, serviceTicket));
+
+ GSSName srcName = gssContext.getSrcName();
+ if (srcName == null) {
+ throw ExceptionUtils.toNotAuthorizedException(null, getFaultResponse());
+ }
+
+ String complexUserName = srcName.toString();
+
+ String simpleUserName = complexUserName;
+ int index = simpleUserName.lastIndexOf('@');
+ if (index > 0) {
+ simpleUserName = simpleUserName.substring(0, index);
+ }
+ Message m = JAXRSUtils.getCurrentMessage();
+ m.put(SecurityContext.class, createSecurityContext(simpleUserName, complexUserName, gssContext));
+
+ if (!gssContext.getCredDelegState()) {
+ gssContext.dispose();
+ }
+
+ } catch (LoginException e) {
+ LOG.fine("Unsuccessful JAAS login for the service principal: " + e.getMessage());
+ throw ExceptionUtils.toNotAuthorizedException(e, getFaultResponse());
+ } catch (GSSException e) {
+ LOG.fine("GSS API exception: " + e.getMessage());
+ throw ExceptionUtils.toNotAuthorizedException(e, getFaultResponse());
+ } catch (PrivilegedActionException e) {
+ LOG.fine("PrivilegedActionException: " + e.getMessage());
+ throw ExceptionUtils.toNotAuthorizedException(e, getFaultResponse());
+ }
+ }
+
+ protected SecurityContext createSecurityContext(String simpleUserName, String complexUserName,
+ GSSContext gssContext) {
+ return new KerberosSecurityContext(new KerberosPrincipal(simpleUserName, complexUserName), gssContext);
+ }
+
+ protected GSSContext createGSSContext() throws GSSException {
+ boolean useKerberosOid = PropertyUtils.isTrue(
+ messageContext.getContextualProperty(PROPERTY_USE_KERBEROS_OID));
+ Oid oid = new Oid(useKerberosOid ? KERBEROS_OID : SPNEGO_OID);
+
+ GSSManager gssManager = GSSManager.getInstance();
+
+ String spn = getCompleteServicePrincipalName();
+ GSSName gssService = gssManager.createName(spn, null);
+
+ return gssManager.createContext(gssService.canonicalize(oid),
+ oid, null, GSSContext.DEFAULT_LIFETIME);
+ }
+
+ protected Subject loginAndGetSubject() throws LoginException {
+
+ // The login without a callback can work if
+ // - Kerberos keytabs are used with a principal name set in the JAAS config
+ // - Kerberos is integrated into the OS logon process
+ // meaning that a process which runs this code has the
+ // user identity
+
+ final LoginContext lc;
+ if (!StringUtils.isEmpty(loginContextName) || loginConfig != null) {
+ lc = new LoginContext(loginContextName, null, callbackHandler, loginConfig);
+ } else {
+ LOG.fine("LoginContext can not be initialized");
+ throw new LoginException();
+ }
+ lc.login();
+ return lc.getSubject();
+ }
+
+ private byte[] getServiceTicket(String encodedServiceTicket) {
+ try {
+ return Base64Utility.decode(encodedServiceTicket);
+ } catch (Base64Exception ex) {
+ throw ExceptionUtils.toNotAuthorizedException(null, getFaultResponse());
+ }
+ }
+
+ private static Response getFaultResponse() {
+ return JAXRSUtils.toResponseBuilder(401).header(HttpHeaders.WWW_AUTHENTICATE, NEGOTIATE_SCHEME).build();
+ }
+
+ protected String getCompleteServicePrincipalName() {
+ String name = servicePrincipalName == null
+ ? "HTTP/" + messageContext.getUriInfo().getBaseUri().getHost() : servicePrincipalName;
+ if (realm != null) {
+ name += "@" + realm;
+ }
+ return name;
+
+
+ }
+
+ @Context
+ public void setMessageContext(MessageContext context) {
+ this.messageContext = context;
+ }
+
+ public void setLoginContextName(String contextName) {
+ this.loginContextName = contextName;
+ }
+
+ public void setServicePrincipalName(String servicePrincipalName) {
+ this.servicePrincipalName = servicePrincipalName;
+ }
+
+ public void setRealm(String realm) {
+ this.realm = realm;
+ }
+
+ public void setCallbackHandler(CallbackHandler callbackHandler) {
+ this.callbackHandler = callbackHandler;
+ }
+
+ private static final class ValidateServiceTicketAction implements PrivilegedExceptionAction<byte[]> {
+ private final GSSContext context;
+ private final byte[] token;
+
+ private ValidateServiceTicketAction(GSSContext context, byte[] token) {
+ this.context = context;
+ this.token = token;
+ }
+
+ public byte[] run() throws GSSException {
+ return context.acceptSecContext(token, 0, token.length);
+ }
+ }
+
+ public static class KerberosPrincipal extends SimplePrincipal {
+ private static final long serialVersionUID = 1L;
+ private String complexName;
+ public KerberosPrincipal(String simpleName, String complexName) {
+ super(simpleName);
+ this.complexName = complexName;
+ }
+
+ public String getKerberosName() {
+ return complexName;
+ }
+ }
+
+ public static class KerberosSecurityContext extends SimpleSecurityContext {
+ private GSSContext context;
+ public KerberosSecurityContext(KerberosPrincipal principal,
+ GSSContext context) {
+ super(principal);
+ this.context = context;
+ }
+
+ public GSSContext getGSSContext() {
+ return context;
+ }
+ }
+
+
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/servlet/CXFNonSpringJaxrsServlet.java b/transform/src/patch/java/org/apache/cxf/jaxrs/servlet/CXFNonSpringJaxrsServlet.java
new file mode 100644
index 0000000..290c223
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/servlet/CXFNonSpringJaxrsServlet.java
@@ -0,0 +1,640 @@
+/**
+ * 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.jaxrs.servlet;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.ws.rs.core.Application;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.PrimitiveUtils;
+import org.apache.cxf.common.util.PropertyUtils;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.feature.Feature;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.interceptor.Interceptor;
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.lifecycle.PerRequestResourceProvider;
+import org.apache.cxf.jaxrs.lifecycle.ResourceProvider;
+import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
+import org.apache.cxf.jaxrs.model.ApplicationInfo;
+import org.apache.cxf.jaxrs.model.ProviderInfo;
+import org.apache.cxf.jaxrs.provider.ProviderFactory;
+import org.apache.cxf.jaxrs.utils.InjectionUtils;
+import org.apache.cxf.jaxrs.utils.ResourceUtils;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.service.invoker.Invoker;
+import org.apache.cxf.transport.http.DestinationRegistry;
+import org.apache.cxf.transport.servlet.CXFNonSpringServlet;
+
+public class CXFNonSpringJaxrsServlet extends CXFNonSpringServlet {
+
+ private static final long serialVersionUID = -8916352798780577499L;
+
+ private static final Logger LOG = LogUtils.getL7dLogger(CXFNonSpringJaxrsServlet.class);
+
+ private static final String USER_MODEL_PARAM = "user.model";
+ private static final String SERVICE_ADDRESS_PARAM = "jaxrs.address";
+ private static final String IGNORE_APP_PATH_PARAM = "jaxrs.application.address.ignore";
+ private static final String SERVICE_CLASSES_PARAM = "jaxrs.serviceClasses";
+ private static final String PROVIDERS_PARAM = "jaxrs.providers";
+ private static final String FEATURES_PARAM = "jaxrs.features";
+ private static final String OUT_INTERCEPTORS_PARAM = "jaxrs.outInterceptors";
+ private static final String OUT_FAULT_INTERCEPTORS_PARAM = "jaxrs.outFaultInterceptors";
+ private static final String IN_INTERCEPTORS_PARAM = "jaxrs.inInterceptors";
+ private static final String INVOKER_PARAM = "jaxrs.invoker";
+ private static final String SERVICE_SCOPE_PARAM = "jaxrs.scope";
+ private static final String EXTENSIONS_PARAM = "jaxrs.extensions";
+ private static final String LANGUAGES_PARAM = "jaxrs.languages";
+ private static final String PROPERTIES_PARAM = "jaxrs.properties";
+ private static final String SCHEMAS_PARAM = "jaxrs.schemaLocations";
+ private static final String DOC_LOCATION_PARAM = "jaxrs.documentLocation";
+ private static final String STATIC_SUB_RESOLUTION_PARAM = "jaxrs.static.subresources";
+ private static final String SERVICE_SCOPE_SINGLETON = "singleton";
+ private static final String SERVICE_SCOPE_REQUEST = "prototype";
+
+ private static final String PARAMETER_SPLIT_CHAR = "class.parameter.split.char";
+ private static final String DEFAULT_PARAMETER_SPLIT_CHAR = ",";
+ private static final String SPACE_PARAMETER_SPLIT_CHAR = "space";
+
+ private static final String JAXRS_APPLICATION_PARAM = "javax.ws.rs.Application";
+
+ private ClassLoader classLoader;
+ private Application application;
+
+ public CXFNonSpringJaxrsServlet() {
+
+ }
+
+ public CXFNonSpringJaxrsServlet(Application app) {
+ this.application = app;
+ }
+
+ public CXFNonSpringJaxrsServlet(Object singletonService) {
+ this(Collections.singleton(singletonService));
+ }
+ public CXFNonSpringJaxrsServlet(Set<Object> applicationSingletons) {
+ this(new ApplicationImpl(applicationSingletons));
+ }
+
+ public CXFNonSpringJaxrsServlet(Application app, DestinationRegistry destinationRegistry, Bus bus) {
+ super(destinationRegistry, bus);
+ this.application = app;
+ }
+
+ @Override
+ public void init(ServletConfig servletConfig) throws ServletException {
+ super.init(servletConfig);
+
+ if (getApplication() != null) {
+ createServerFromApplication(servletConfig);
+ return;
+ }
+
+ String applicationClass = servletConfig.getInitParameter(JAXRS_APPLICATION_PARAM);
+ if (applicationClass != null) {
+ createServerFromApplication(applicationClass, servletConfig);
+ return;
+ }
+
+ String splitChar = getParameterSplitChar(servletConfig);
+ JAXRSServerFactoryBean bean = new JAXRSServerFactoryBean();
+ bean.setBus(getBus());
+
+ String address = servletConfig.getInitParameter(SERVICE_ADDRESS_PARAM);
+ if (address == null) {
+ address = "/";
+ }
+ bean.setAddress(address);
+
+ bean.setStaticSubresourceResolution(getStaticSubResolutionValue(servletConfig));
+
+ String modelRef = servletConfig.getInitParameter(USER_MODEL_PARAM);
+ if (modelRef != null) {
+ bean.setModelRef(modelRef.trim());
+ }
+ setDocLocation(bean, servletConfig);
+ setSchemasLocations(bean, servletConfig);
+ setAllInterceptors(bean, servletConfig, splitChar);
+ setInvoker(bean, servletConfig);
+
+ Map<Class<?>, Map<String, List<String>>> resourceClasses =
+ getServiceClasses(servletConfig, modelRef != null, splitChar);
+ Map<Class<?>, ResourceProvider> resourceProviders =
+ getResourceProviders(servletConfig, resourceClasses);
+
+ List<?> providers = getProviders(servletConfig, splitChar);
+
+ bean.setResourceClasses(new ArrayList<Class<?>>(resourceClasses.keySet()));
+ bean.setProviders(providers);
+ for (Map.Entry<Class<?>, ResourceProvider> entry : resourceProviders.entrySet()) {
+ bean.setResourceProvider(entry.getKey(), entry.getValue());
+ }
+ setExtensions(bean, servletConfig);
+
+ List<? extends Feature> features = getFeatures(servletConfig, splitChar);
+ bean.getFeatures().addAll(features);
+
+ bean.create();
+ }
+
+ protected String getParameterSplitChar(ServletConfig servletConfig) {
+ String param = servletConfig.getInitParameter(PARAMETER_SPLIT_CHAR);
+ if (!StringUtils.isEmpty(param) && SPACE_PARAMETER_SPLIT_CHAR.equals(param.trim())) {
+ return " ";
+ }
+ return DEFAULT_PARAMETER_SPLIT_CHAR;
+ }
+ protected boolean getStaticSubResolutionValue(ServletConfig servletConfig) {
+ String param = servletConfig.getInitParameter(STATIC_SUB_RESOLUTION_PARAM);
+ if (param != null) {
+ return Boolean.valueOf(param.trim());
+ }
+ return false;
+ }
+
+ protected void setExtensions(JAXRSServerFactoryBean bean, ServletConfig servletConfig) {
+ bean.setExtensionMappings(
+ CastUtils.cast((Map<?, ?>)parseMapSequence(servletConfig.getInitParameter(EXTENSIONS_PARAM))));
+ bean.setLanguageMappings(
+ CastUtils.cast((Map<?, ?>)parseMapSequence(servletConfig.getInitParameter(LANGUAGES_PARAM))));
+ Map<String, Object> properties = CastUtils.cast(
+ parseMapSequence(servletConfig.getInitParameter(PROPERTIES_PARAM)),
+ String.class, Object.class);
+ if (properties != null) {
+ bean.getProperties(true).putAll(properties);
+ }
+ }
+
+ protected void setAllInterceptors(JAXRSServerFactoryBean bean, ServletConfig servletConfig,
+ String splitChar)
+ throws ServletException {
+ setInterceptors(bean, servletConfig, OUT_INTERCEPTORS_PARAM, splitChar);
+ setInterceptors(bean, servletConfig, OUT_FAULT_INTERCEPTORS_PARAM, splitChar);
+ setInterceptors(bean, servletConfig, IN_INTERCEPTORS_PARAM, splitChar);
+ }
+
+ protected void setSchemasLocations(JAXRSServerFactoryBean bean, ServletConfig servletConfig) {
+ String schemas = servletConfig.getInitParameter(SCHEMAS_PARAM);
+ if (schemas == null) {
+ return;
+ }
+ String[] locations = schemas.split(" ");
+ List<String> list = new ArrayList<>();
+ for (String loc : locations) {
+ String theLoc = loc.trim();
+ if (!theLoc.isEmpty()) {
+ list.add(theLoc);
+ }
+ }
+ if (!list.isEmpty()) {
+ bean.setSchemaLocations(list);
+ }
+ }
+
+ protected void setDocLocation(JAXRSServerFactoryBean bean, ServletConfig servletConfig) {
+ String wadlLoc = servletConfig.getInitParameter(DOC_LOCATION_PARAM);
+ if (wadlLoc != null) {
+ bean.setDocLocation(wadlLoc);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ protected void setInterceptors(JAXRSServerFactoryBean bean, ServletConfig servletConfig,
+ String paramName,
+ String splitChar) throws ServletException {
+ String value = servletConfig.getInitParameter(paramName);
+ if (value == null) {
+ return;
+ }
+ String[] values = value.split(splitChar);
+ List<Interceptor<? extends Message>> list = new ArrayList<>();
+ for (String interceptorVal : values) {
+ Map<String, List<String>> props = new HashMap<>();
+ String theValue = getClassNameAndProperties(interceptorVal, props);
+ if (!theValue.isEmpty()) {
+ try {
+ Class<?> intClass = loadClass(theValue, "Interceptor");
+ Object object = intClass.newInstance();
+ injectProperties(object, props);
+ list.add((Interceptor<? extends Message>)object);
+ } catch (ServletException ex) {
+ throw ex;
+ } catch (Exception ex) {
+ LOG.warning("Interceptor class " + theValue + " can not be created");
+ throw new ServletException(ex);
+ }
+ }
+ }
+ if (!list.isEmpty()) {
+ if (OUT_INTERCEPTORS_PARAM.equals(paramName)) {
+ bean.setOutInterceptors(list);
+ } else if (OUT_FAULT_INTERCEPTORS_PARAM.equals(paramName)) {
+ bean.setOutFaultInterceptors(list);
+ } else {
+ bean.setInInterceptors(list);
+ }
+ }
+ }
+
+ protected void setInvoker(JAXRSServerFactoryBean bean, ServletConfig servletConfig)
+ throws ServletException {
+ String value = servletConfig.getInitParameter(INVOKER_PARAM);
+ if (value == null) {
+ return;
+ }
+ Map<String, List<String>> props = new HashMap<>();
+ String theValue = getClassNameAndProperties(value, props);
+ if (!theValue.isEmpty()) {
+ try {
+ Class<?> intClass = loadClass(theValue, "Invoker");
+ Object object = intClass.newInstance();
+ injectProperties(object, props);
+ bean.setInvoker((Invoker)object);
+ } catch (ServletException ex) {
+ throw ex;
+ } catch (Exception ex) {
+ LOG.warning("Invoker class " + theValue + " can not be created");
+ throw new ServletException(ex);
+ }
+ }
+
+
+ }
+
+ protected Map<Class<?>, Map<String, List<String>>> getServiceClasses(ServletConfig servletConfig,
+ boolean modelAvailable,
+ String splitChar) throws ServletException {
+ String serviceBeans = servletConfig.getInitParameter(SERVICE_CLASSES_PARAM);
+ if (serviceBeans == null) {
+ if (modelAvailable) {
+ return Collections.emptyMap();
+ }
+ throw new ServletException("At least one resource class should be specified");
+ }
+ String[] classNames = serviceBeans.split(splitChar);
+ Map<Class<?>, Map<String, List<String>>> map = new HashMap<>();
+ for (String cName : classNames) {
+ Map<String, List<String>> props = new HashMap<>();
+ String theName = getClassNameAndProperties(cName, props);
+ if (!theName.isEmpty()) {
+ Class<?> cls = loadClass(theName);
+ map.put(cls, props);
+ }
+ }
+ if (map.isEmpty()) {
+ throw new ServletException("At least one resource class should be specified");
+ }
+ return map;
+ }
+
+ protected List<? extends Feature> getFeatures(ServletConfig servletConfig, String splitChar)
+ throws ServletException {
+
+ String featuresList = servletConfig.getInitParameter(FEATURES_PARAM);
+ if (featuresList == null) {
+ return Collections.< Feature >emptyList();
+ }
+ String[] classNames = featuresList.split(splitChar);
+ List< Feature > features = new ArrayList<>();
+ for (String cName : classNames) {
+ Map<String, List<String>> props = new HashMap<>();
+ String theName = getClassNameAndProperties(cName, props);
+ if (!theName.isEmpty()) {
+ Class<?> cls = loadClass(theName);
+ if (Feature.class.isAssignableFrom(cls)) {
+ features.add((Feature)createSingletonInstance(cls, props, servletConfig));
+ }
+ }
+ }
+ return features;
+ }
+
+ protected List<?> getProviders(ServletConfig servletConfig, String splitChar) throws ServletException {
+ String providersList = servletConfig.getInitParameter(PROVIDERS_PARAM);
+ if (providersList == null) {
+ return Collections.emptyList();
+ }
+ String[] classNames = providersList.split(splitChar);
+ List<Object> providers = new ArrayList<>();
+ for (String cName : classNames) {
+ Map<String, List<String>> props = new HashMap<>();
+ String theName = getClassNameAndProperties(cName, props);
+ if (!theName.isEmpty()) {
+ Class<?> cls = loadClass(theName);
+ providers.add(createSingletonInstance(cls, props, servletConfig));
+ }
+ }
+ return providers;
+ }
+
+ private String getClassNameAndProperties(String cName, Map<String, List<String>> props) {
+ String theName = cName.trim();
+ int ind = theName.indexOf('(');
+ if (ind != -1 && theName.endsWith(")")) {
+ props.putAll(parseMapListSequence(theName.substring(ind + 1, theName.length() - 1)));
+ theName = theName.substring(0, ind).trim();
+ }
+ return theName;
+ }
+
+ protected static Map<String, List<String>> parseMapListSequence(String sequence) {
+ if (sequence != null) {
+ sequence = sequence.trim();
+ Map<String, List<String>> map = new HashMap<>();
+ String[] pairs = sequence.split(" ");
+ for (String pair : pairs) {
+ String thePair = pair.trim();
+ if (thePair.length() == 0) {
+ continue;
+ }
+ String[] values = thePair.split("=");
+ String key;
+ String value;
+ if (values.length == 2) {
+ key = values[0].trim();
+ value = values[1].trim();
+ } else {
+ key = thePair;
+ value = "";
+ }
+ List<String> list = map.get(key);
+ if (list == null) {
+ list = new LinkedList<>();
+ map.put(key, list);
+ }
+ list.add(value);
+ }
+ return map;
+ }
+ return Collections.emptyMap();
+ }
+
+ protected Map<Class<?>, ResourceProvider> getResourceProviders(ServletConfig servletConfig,
+ Map<Class<?>, Map<String, List<String>>> resourceClasses) throws ServletException {
+ String scope = servletConfig.getInitParameter(SERVICE_SCOPE_PARAM);
+ if (scope != null && !SERVICE_SCOPE_SINGLETON.equals(scope)
+ && !SERVICE_SCOPE_REQUEST.equals(scope)) {
+ throw new ServletException("Only singleton and prototype scopes are supported");
+ }
+ boolean isPrototype = SERVICE_SCOPE_REQUEST.equals(scope);
+ Map<Class<?>, ResourceProvider> map = new HashMap<>();
+ for (Map.Entry<Class<?>, Map<String, List<String>>> entry : resourceClasses.entrySet()) {
+ Class<?> c = entry.getKey();
+ map.put(c, isPrototype ? new PerRequestResourceProvider(c)
+ : new SingletonResourceProvider(
+ createSingletonInstance(c, entry.getValue(), servletConfig),
+ true));
+ }
+ return map;
+ }
+
+ protected boolean isAppResourceLifecycleASingleton(Application app, ServletConfig servletConfig) {
+ String scope = servletConfig.getInitParameter(SERVICE_SCOPE_PARAM);
+ if (scope == null) {
+ scope = (String)app.getProperties().get(SERVICE_SCOPE_PARAM);
+ }
+ return SERVICE_SCOPE_SINGLETON.equals(scope);
+ }
+
+ protected Object createSingletonInstance(Class<?> cls, Map<String, List<String>> props, ServletConfig sc)
+ throws ServletException {
+ Constructor<?> c = ResourceUtils.findResourceConstructor(cls, false);
+ if (c == null) {
+ throw new ServletException("No valid constructor found for " + cls.getName());
+ }
+ boolean isApplication = Application.class.isAssignableFrom(c.getDeclaringClass());
+ try {
+ final ProviderInfo<? extends Object> provider;
+ if (c.getParameterTypes().length == 0) {
+ if (isApplication) {
+ provider = new ApplicationInfo((Application)c.newInstance(), getBus());
+ } else {
+ provider = new ProviderInfo<>(c.newInstance(), getBus(), false, true);
+ }
+ } else {
+ Map<Class<?>, Object> values = new HashMap<>();
+ values.put(ServletContext.class, sc.getServletContext());
+ values.put(ServletConfig.class, sc);
+ provider = ProviderFactory.createProviderFromConstructor(c, values, getBus(), isApplication, true);
+ }
+ Object instance = provider.getProvider();
+ injectProperties(instance, props);
+ configureSingleton(instance);
+ return isApplication ? provider : instance;
+ } catch (InstantiationException ex) {
+ ex.printStackTrace();
+ throw new ServletException("Resource class " + cls.getName()
+ + " can not be instantiated");
+ } catch (IllegalAccessException ex) {
+ ex.printStackTrace();
+ throw new ServletException("Resource class " + cls.getName()
+ + " can not be instantiated due to IllegalAccessException");
+ } catch (InvocationTargetException ex) {
+ ex.printStackTrace();
+ throw new ServletException("Resource class " + cls.getName()
+ + " can not be instantiated due to InvocationTargetException");
+ }
+ }
+
+ private void injectProperties(Object instance, Map<String, List<String>> props) {
+ if (props == null || props.isEmpty()) {
+ return;
+ }
+ Method[] methods = instance.getClass().getMethods();
+ Map<String, Method> methodsMap = new HashMap<>();
+ for (Method m : methods) {
+ methodsMap.put(m.getName(), m);
+ }
+ for (Map.Entry<String, List<String>> entry : props.entrySet()) {
+ Method m = methodsMap.get("set" + StringUtils.capitalize(entry.getKey()));
+ if (m != null) {
+ Class<?> type = m.getParameterTypes()[0];
+ Object value;
+ if (InjectionUtils.isPrimitive(type)) {
+ value = PrimitiveUtils.read(entry.getValue().get(0), type);
+ } else {
+ value = entry.getValue();
+ }
+ InjectionUtils.injectThroughMethod(instance, m, value);
+ }
+ }
+ }
+
+ protected void configureSingleton(Object instance) {
+
+ }
+
+ protected void createServerFromApplication(String applicationNames, ServletConfig servletConfig)
+ throws ServletException {
+
+ boolean ignoreApplicationPath = isIgnoreApplicationPath(servletConfig);
+
+ String[] classNames = applicationNames.split(getParameterSplitChar(servletConfig));
+
+ if (classNames.length > 1 && ignoreApplicationPath) {
+ throw new ServletException("\"" + IGNORE_APP_PATH_PARAM
+ + "\" parameter must be set to false for multiple Applications be supported");
+ }
+
+ for (String cName : classNames) {
+ ApplicationInfo providerApp = createApplicationInfo(cName, servletConfig);
+
+ Application app = providerApp.getProvider();
+ JAXRSServerFactoryBean bean = ResourceUtils.createApplication(
+ app,
+ ignoreApplicationPath,
+ getStaticSubResolutionValue(servletConfig),
+ isAppResourceLifecycleASingleton(app, servletConfig),
+ getBus());
+ String splitChar = getParameterSplitChar(servletConfig);
+ setAllInterceptors(bean, servletConfig, splitChar);
+ setInvoker(bean, servletConfig);
+ setExtensions(bean, servletConfig);
+ setDocLocation(bean, servletConfig);
+ setSchemasLocations(bean, servletConfig);
+
+ List<?> providers = getProviders(servletConfig, splitChar);
+ bean.setProviders(providers);
+ List<? extends Feature> features = getFeatures(servletConfig, splitChar);
+ bean.getFeatures().addAll(features);
+
+ bean.setBus(getBus());
+ bean.setApplicationInfo(providerApp);
+ bean.create();
+ }
+ }
+
+ protected boolean isIgnoreApplicationPath(ServletConfig servletConfig) {
+ String ignoreParam = servletConfig.getInitParameter(IGNORE_APP_PATH_PARAM);
+ return ignoreParam == null || PropertyUtils.isTrue(ignoreParam);
+ }
+
+ protected void createServerFromApplication(ServletConfig servletConfig)
+ throws ServletException {
+
+ Application app = getApplication();
+ JAXRSServerFactoryBean bean = ResourceUtils.createApplication(
+ app,
+ isIgnoreApplicationPath(servletConfig),
+ getStaticSubResolutionValue(servletConfig),
+ isAppResourceLifecycleASingleton(app, servletConfig),
+ getBus());
+ String splitChar = getParameterSplitChar(servletConfig);
+ setAllInterceptors(bean, servletConfig, splitChar);
+ setInvoker(bean, servletConfig);
+ setExtensions(bean, servletConfig);
+ setDocLocation(bean, servletConfig);
+ setSchemasLocations(bean, servletConfig);
+
+ List<?> providers = getProviders(servletConfig, splitChar);
+ bean.setProviders(providers);
+ List<? extends Feature> features = getFeatures(servletConfig, splitChar);
+ bean.getFeatures().addAll(features);
+
+ bean.setBus(getBus());
+ bean.setApplication(getApplication());
+ bean.create();
+ }
+
+ protected Application createApplicationInstance(String appClassName, ServletConfig servletConfig)
+ throws ServletException {
+ return null;
+ }
+ protected ApplicationInfo createApplicationInfo(String appClassName, ServletConfig servletConfig)
+ throws ServletException {
+
+ Application customApp = createApplicationInstance(appClassName, servletConfig);
+ if (customApp != null) {
+ return new ApplicationInfo(customApp, getBus());
+ }
+ Map<String, List<String>> props = new HashMap<>();
+ appClassName = getClassNameAndProperties(appClassName, props);
+ Class<?> appClass = loadApplicationClass(appClassName);
+ ApplicationInfo appInfo = (ApplicationInfo)createSingletonInstance(appClass, props, servletConfig);
+ Map<String, Object> servletProps = new HashMap<>();
+ ServletContext servletContext = servletConfig.getServletContext();
+ for (Enumeration<String> names = servletContext.getInitParameterNames(); names.hasMoreElements();) {
+ String name = names.nextElement();
+ servletProps.put(name, servletContext.getInitParameter(name));
+ }
+ for (Enumeration<String> names = servletConfig.getInitParameterNames(); names.hasMoreElements();) {
+ String name = names.nextElement();
+ servletProps.put(name, servletConfig.getInitParameter(name));
+ }
+ appInfo.setOverridingProps(servletProps);
+ return appInfo;
+ }
+
+ protected Class<?> loadApplicationClass(String appClassName) throws ServletException {
+ return loadClass(appClassName, "Application");
+ }
+
+ protected Class<?> loadClass(String cName) throws ServletException {
+ return loadClass(cName, "Resource");
+ }
+
+ protected Class<?> loadClass(String cName, String classType) throws ServletException {
+ try {
+ final Class<?> cls;
+ if (classLoader == null) {
+ cls = ClassLoaderUtils.loadClass(cName, CXFNonSpringJaxrsServlet.class);
+ } else {
+ cls = classLoader.loadClass(cName);
+ }
+ return cls;
+ } catch (ClassNotFoundException ex) {
+ throw new ServletException("No " + classType + " class " + cName.trim() + " can be found", ex);
+ }
+ }
+
+ public void setClassLoader(ClassLoader loader) {
+ this.classLoader = loader;
+ }
+
+ protected Application getApplication() {
+ return application;
+ }
+
+ private static class ApplicationImpl extends Application {
+ private Set<Object> applicationSingletons;
+ ApplicationImpl(Set<Object> applicationSingletons) {
+ this.applicationSingletons = applicationSingletons;
+ }
+ public Set<Object> getSingletons() {
+ return applicationSingletons;
+ }
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/spring/JAXRSServerFactoryBeanDefinitionParser.java b/transform/src/patch/java/org/apache/cxf/jaxrs/spring/JAXRSServerFactoryBeanDefinitionParser.java
new file mode 100644
index 0000000..2115f6d
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/spring/JAXRSServerFactoryBeanDefinitionParser.java
@@ -0,0 +1,286 @@
+/**
+ * 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.jaxrs.spring;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.ext.Provider;
+import javax.xml.namespace.QName;
+
+import org.w3c.dom.Element;
+
+import org.apache.cxf.bus.spring.BusWiringBeanFactoryPostProcessor;
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.ClasspathScanner;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.configuration.spring.AbstractBeanDefinitionParser;
+import org.apache.cxf.endpoint.Server;
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.JAXRSServiceFactoryBean;
+import org.apache.cxf.jaxrs.lifecycle.ResourceProvider;
+import org.apache.cxf.jaxrs.model.UserResource;
+import org.apache.cxf.jaxrs.utils.ExceptionUtils;
+import org.apache.cxf.jaxrs.utils.ResourceUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.beans.factory.BeanDefinitionStoreException;
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+
+
+public class JAXRSServerFactoryBeanDefinitionParser extends AbstractBeanDefinitionParser {
+ private static final Logger LOG = LogUtils.getL7dLogger(JAXRSServerFactoryBeanDefinitionParser.class);
+
+ public JAXRSServerFactoryBeanDefinitionParser() {
+ super();
+ setBeanClass(SpringJAXRSServerFactoryBean.class);
+ }
+
+ @Override
+ protected void mapAttribute(BeanDefinitionBuilder bean, Element e, String name, String val) {
+ if ("beanNames".equals(name)) {
+ String[] values = val.split(" ");
+ List<SpringResourceFactory> tempFactories = new ArrayList<>(values.length);
+ for (String v : values) {
+ String theValue = v.trim();
+ if (theValue.length() > 0) {
+ tempFactories.add(new SpringResourceFactory(theValue));
+ }
+ }
+ bean.addPropertyValue("tempResourceProviders", tempFactories);
+ } else if ("serviceName".equals(name)) {
+ QName q = parseQName(e, val);
+ bean.addPropertyValue(name, q);
+ } else if ("basePackages".equals(name)) {
+ bean.addPropertyValue("basePackages", ClasspathScanner.parsePackages(val));
+ } else if ("serviceAnnotation".equals(name)) {
+ bean.addPropertyValue("serviceAnnotation", val);
+ } else if ("publish".equals(name)) {
+ mapToProperty(bean, "start", val);
+ } else {
+ mapToProperty(bean, name, val);
+ }
+ }
+
+ @Override
+ protected void mapElement(ParserContext ctx, BeanDefinitionBuilder bean, Element el, String name) {
+ if ("properties".equals(name)
+ || "extensionMappings".equals(name)
+ || "languageMappings".equals(name)) {
+ Map<?, ?> map = ctx.getDelegate().parseMapElement(el, bean.getBeanDefinition());
+ bean.addPropertyValue(name, map);
+ } else if ("executor".equals(name)) {
+ setFirstChildAsProperty(el, ctx, bean, "serviceFactory.executor");
+ } else if ("invoker".equals(name)) {
+ setFirstChildAsProperty(el, ctx, bean, "serviceFactory.invoker");
+ } else if ("binding".equals(name)) {
+ setFirstChildAsProperty(el, ctx, bean, "bindingConfig");
+ } else if ("inInterceptors".equals(name) || "inFaultInterceptors".equals(name)
+ || "outInterceptors".equals(name) || "outFaultInterceptors".equals(name)) {
+ List<?> list = ctx.getDelegate().parseListElement(el, bean.getBeanDefinition());
+ bean.addPropertyValue(name, list);
+ } else if ("features".equals(name) || "schemaLocations".equals(name)
+ || "providers".equals(name) || "serviceBeans".equals(name)
+ || "modelBeans".equals(name)) {
+ List<?> list = ctx.getDelegate().parseListElement(el, bean.getBeanDefinition());
+ bean.addPropertyValue(name, list);
+ } else if ("serviceFactories".equals(name)) {
+ List<?> list = ctx.getDelegate().parseListElement(el, bean.getBeanDefinition());
+ bean.addPropertyValue("resourceProviders", list);
+ } else if ("model".equals(name)) {
+ List<UserResource> resources = ResourceUtils.getResourcesFromElement(el);
+ bean.addPropertyValue("modelBeans", resources);
+ } else {
+ setFirstChildAsProperty(el, ctx, bean, name);
+ }
+ }
+
+
+ @Override
+ protected void doParse(Element element, ParserContext ctx, BeanDefinitionBuilder bean) {
+ super.doParse(element, ctx, bean);
+
+ bean.setInitMethodName("create");
+ bean.setDestroyMethodName("destroy");
+
+ // We don't really want to delay the registration of our Server
+ bean.setLazyInit(false);
+ }
+
+ @Override
+ protected String resolveId(Element elem,
+ AbstractBeanDefinition definition,
+ ParserContext ctx)
+ throws BeanDefinitionStoreException {
+ String id = super.resolveId(elem, definition, ctx);
+ if (StringUtils.isEmpty(id)) {
+ id = getBeanClass().getName() + "--" + definition.hashCode();
+ }
+
+ return id;
+ }
+
+ @Override
+ protected boolean hasBusProperty() {
+ return true;
+ }
+
+ public static class SpringJAXRSServerFactoryBean extends JAXRSServerFactoryBean implements
+ ApplicationContextAware {
+
+ private List<SpringResourceFactory> tempFactories;
+ private List<String> basePackages;
+ private String serviceAnnotation;
+ private ApplicationContext context;
+ private boolean serviceBeansAvailable;
+ private boolean providerBeansAvailable;
+ private boolean resourceProvidersAvailable;
+
+ public SpringJAXRSServerFactoryBean() {
+ super();
+ }
+
+ public SpringJAXRSServerFactoryBean(JAXRSServiceFactoryBean sf) {
+ super(sf);
+ }
+
+ public void destroy() {
+ Server server = super.getServer();
+ if (server != null && server.isStarted()) {
+ server.destroy();
+ }
+ }
+ @Override
+ public void setServiceBeans(List<Object> beans) {
+ super.setServiceBeans(beans);
+ this.serviceBeansAvailable = true;
+ }
+ @Override
+ public void setProviders(List<? extends Object> beans) {
+ super.setProviders(beans);
+ this.providerBeansAvailable = true;
+ }
+ public void setResourceProviders(List<ResourceProvider> rps) {
+ super.setResourceProviders(rps);
+ this.resourceProvidersAvailable = true;
+ }
+ public void setBasePackages(List<String> basePackages) {
+ this.basePackages = basePackages;
+ }
+
+ public void setServiceAnnotation(String serviceAnnotation) {
+ this.serviceAnnotation = serviceAnnotation;
+ }
+
+ public void setTempResourceProviders(List<SpringResourceFactory> providers) {
+ tempFactories = providers;
+ }
+
+ public void setApplicationContext(ApplicationContext ctx) throws BeansException {
+ this.context = ctx;
+
+ if (tempFactories != null) {
+ List<ResourceProvider> factories = new ArrayList<>(
+ tempFactories.size());
+ for (int i = 0; i < tempFactories.size(); i++) {
+ SpringResourceFactory factory = tempFactories.get(i);
+ factory.setApplicationContext(ctx);
+ factories.add(factory);
+ }
+ tempFactories.clear();
+ setResourceProviders(factories);
+ }
+ Class<? extends Annotation> serviceAnnotationClass = loadServiceAnnotationClass();
+ if (basePackages != null) {
+ try {
+ final Map< Class< ? extends Annotation >, Collection< Class< ? > > > classes =
+ ClasspathScanner.findClasses(basePackages, Provider.class, Path.class);
+
+ this.setServiceBeans(createBeansFromDiscoveredClasses(context,
+ classes.get(Path.class),
+ serviceAnnotationClass));
+ this.setProviders(createBeansFromDiscoveredClasses(context,
+ classes.get(Provider.class),
+ serviceAnnotationClass));
+ } catch (IOException ex) {
+ throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
+ } catch (ClassNotFoundException ex) {
+ throw new BeanCreationException("Failed to create bean from classfile", ex);
+ }
+ } else if (serviceAnnotationClass != null
+ && !serviceBeansAvailable && !providerBeansAvailable && !resourceProvidersAvailable) {
+ discoverContextResources(serviceAnnotationClass);
+ }
+ if (bus == null) {
+ setBus(BusWiringBeanFactoryPostProcessor.addDefaultBus(ctx));
+ }
+ }
+ private void discoverContextResources(Class<? extends Annotation> serviceAnnotationClass) {
+ AbstractSpringComponentScanServer scanServer =
+ new AbstractSpringComponentScanServer(serviceAnnotationClass) { };
+ scanServer.setApplicationContext(context);
+ scanServer.setJaxrsResources(this);
+ }
+ @SuppressWarnings("unchecked")
+ private Class<? extends Annotation> loadServiceAnnotationClass() {
+ if (serviceAnnotation != null) {
+ try {
+ return (Class<? extends Annotation>)ClassLoaderUtils.loadClass(serviceAnnotation, this.getClass());
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ return null;
+ }
+ }
+ static List<Object> createBeansFromDiscoveredClasses(ApplicationContext context,
+ Collection<Class<?>> classes,
+ Class<? extends Annotation> serviceClassAnnotation) {
+ AutowireCapableBeanFactory beanFactory = context.getAutowireCapableBeanFactory();
+ final List< Object > providers = new ArrayList<>();
+ for (final Class< ? > clazz: classes) {
+ if (serviceClassAnnotation != null && clazz.getAnnotation(serviceClassAnnotation) == null) {
+ continue;
+ }
+ Object bean;
+ try {
+ bean = beanFactory.createBean(clazz, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
+ } catch (Exception ex) {
+ String stackTrace = ExceptionUtils.getStackTrace(ex);
+ LOG.fine("Autowire failure for a " + clazz.getName() + " bean: " + stackTrace);
+ bean = beanFactory.createBean(clazz);
+ }
+ providers.add(bean);
+ }
+ return providers;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/utils/HttpUtils.java b/transform/src/patch/java/org/apache/cxf/jaxrs/utils/HttpUtils.java
new file mode 100644
index 0000000..aa42382
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/utils/HttpUtils.java
@@ -0,0 +1,704 @@
+/**
+ * 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.jaxrs.utils;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.PathSegment;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.ext.RuntimeDelegate;
+import javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate;
+
+import org.apache.cxf.common.i18n.BundleUtils;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.PropertyUtils;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.common.util.UrlUtils;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.jaxrs.impl.HttpHeadersImpl;
+import org.apache.cxf.jaxrs.impl.HttpServletRequestFilter;
+import org.apache.cxf.jaxrs.impl.HttpServletResponseFilter;
+import org.apache.cxf.jaxrs.impl.MetadataMap;
+import org.apache.cxf.jaxrs.impl.PathSegmentImpl;
+import org.apache.cxf.jaxrs.impl.RuntimeDelegateImpl;
+import org.apache.cxf.jaxrs.model.ParameterType;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.service.model.EndpointInfo;
+import org.apache.cxf.transport.Destination;
+import org.apache.cxf.transport.http.AbstractHTTPDestination;
+import org.apache.cxf.transport.http.Headers;
+import org.apache.cxf.transport.servlet.BaseUrlHelper;
+
+public final class HttpUtils {
+
+ private static final ResourceBundle BUNDLE = BundleUtils.getBundle(HttpUtils.class);
+ private static final Logger LOG = LogUtils.getL7dLogger(HttpUtils.class);
+
+ private static final String REQUEST_PATH_TO_MATCH = "path_to_match";
+ private static final String REQUEST_PATH_TO_MATCH_SLASH = "path_to_match_slash";
+
+ private static final String HTTP_SCHEME = "http";
+ private static final String LOCAL_HOST_IP_ADDRESS = "127.0.0.1";
+ private static final String REPLACE_LOOPBACK_PROPERTY = "replace.loopback.address.with.localhost";
+ private static final String LOCAL_HOST_IP_ADDRESS_SCHEME = "://" + LOCAL_HOST_IP_ADDRESS;
+ private static final String ANY_IP_ADDRESS = "0.0.0.0";
+ private static final String ANY_IP_ADDRESS_SCHEME = "://" + ANY_IP_ADDRESS;
+ private static final int DEFAULT_HTTP_PORT = 80;
+
+ private static final Pattern ENCODE_PATTERN = Pattern.compile("%[0-9a-fA-F][0-9a-fA-F]");
+ private static final String CHARSET_PARAMETER = "charset";
+ private static final String DOUBLE_QUOTE = "\"";
+
+ // there are more of such characters, ex, '*' but '*' is not affected by UrlEncode
+ private static final String PATH_RESERVED_CHARACTERS = "=@/:!$&\'(),;~";
+ private static final String QUERY_RESERVED_CHARACTERS = "?/,";
+
+ private static final Set<String> KNOWN_HTTP_VERBS_WITH_NO_REQUEST_CONTENT =
+ new HashSet<>(Arrays.asList(new String[]{"GET", "HEAD", "OPTIONS", "TRACE"}));
+ private static final Set<String> KNOWN_HTTP_VERBS_WITH_NO_RESPONSE_CONTENT =
+ new HashSet<>(Arrays.asList(new String[]{"HEAD", "OPTIONS"}));
+
+ private HttpUtils() {
+ }
+
+ public static String urlDecode(String value, String enc) {
+ return UrlUtils.urlDecode(value, enc);
+ }
+
+ public static String urlDecode(String value) {
+ return UrlUtils.urlDecode(value);
+ }
+
+ public static String pathDecode(String value) {
+ return UrlUtils.pathDecode(value);
+ }
+
+ private static String componentEncode(String reservedChars, String value) {
+
+ StringBuilder buffer = null;
+ int length = value.length();
+ int startingIndex = 0;
+ for (int i = 0; i < length; i++) {
+ char currentChar = value.charAt(i);
+ if (reservedChars.indexOf(currentChar) != -1) {
+ if (buffer == null) {
+ buffer = new StringBuilder(length + 8);
+ }
+ // If it is going to be an empty string nothing to encode.
+ if (i != startingIndex) {
+ buffer.append(urlEncode(value.substring(startingIndex, i)));
+ }
+ buffer.append(currentChar);
+ startingIndex = i + 1;
+ }
+ }
+
+ if (buffer == null) {
+ return urlEncode(value);
+ }
+ if (startingIndex < length) {
+ buffer.append(urlEncode(value.substring(startingIndex, length)));
+ }
+
+ return buffer.toString();
+ }
+
+ public static String queryEncode(String value) {
+
+ return componentEncode(QUERY_RESERVED_CHARACTERS, value);
+ }
+
+ public static String urlEncode(String value) {
+
+ return urlEncode(value, StandardCharsets.UTF_8.name());
+ }
+
+ public static String urlEncode(String value, String enc) {
+
+ return UrlUtils.urlEncode(value, enc);
+ }
+
+ public static String pathEncode(String value) {
+
+ String result = componentEncode(PATH_RESERVED_CHARACTERS, value);
+ // URLEncoder will encode '+' to %2B but will turn ' ' into '+'
+ // We need to retain '+' and encode ' ' as %20
+ if (result.indexOf('+') != -1) {
+ result = result.replace("+", "%20");
+ }
+ if (result.indexOf("%2B") != -1) {
+ result = result.replace("%2B", "+");
+ }
+
+ return result;
+ }
+
+ public static boolean isPartiallyEncoded(String value) {
+ return ENCODE_PATTERN.matcher(value).find();
+ }
+
+ /**
+ * Encodes partially encoded string. Encode all values but those matching pattern
+ * "percent char followed by two hexadecimal digits".
+ *
+ * @param encoded fully or partially encoded string.
+ * @return fully encoded string
+ */
+ public static String encodePartiallyEncoded(String encoded, boolean query) {
+ if (encoded.length() == 0) {
+ return encoded;
+ }
+ Matcher m = ENCODE_PATTERN.matcher(encoded);
+
+ if (!m.find()) {
+ return query ? HttpUtils.queryEncode(encoded) : HttpUtils.pathEncode(encoded);
+ }
+
+ int length = encoded.length();
+ StringBuilder sb = new StringBuilder(length + 8);
+ int i = 0;
+ do {
+ String before = encoded.substring(i, m.start());
+ sb.append(query ? HttpUtils.queryEncode(before) : HttpUtils.pathEncode(before));
+ sb.append(m.group());
+ i = m.end();
+ } while (m.find());
+ String tail = encoded.substring(i, length);
+ sb.append(query ? HttpUtils.queryEncode(tail) : HttpUtils.pathEncode(tail));
+ return sb.toString();
+ }
+
+ public static SimpleDateFormat getHttpDateFormat() {
+ return Headers.getHttpDateFormat();
+ }
+
+ public static String toHttpDate(Date date) {
+ return Headers.toHttpDate(date);
+ }
+
+ public static RuntimeDelegate getOtherRuntimeDelegate() {
+ try {
+ RuntimeDelegate rd = RuntimeDelegate.getInstance();
+ return rd instanceof RuntimeDelegateImpl ? null : rd;
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+
+ public static HeaderDelegate<Object> getHeaderDelegate(Object o) {
+ return getHeaderDelegate(RuntimeDelegate.getInstance(), o);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static HeaderDelegate<Object> getHeaderDelegate(RuntimeDelegate rd, Object o) {
+ return rd == null ? null : (HeaderDelegate<Object>)rd.createHeaderDelegate(o.getClass());
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> MultivaluedMap<String, T> getModifiableStringHeaders(Message m) {
+ MultivaluedMap<String, Object> headers = getModifiableHeaders(m);
+ convertHeaderValuesToString(headers, false);
+ return (MultivaluedMap<String, T>)headers;
+ }
+
+ public static MultivaluedMap<String, Object> getModifiableHeaders(Message m) {
+ Map<String, List<Object>> headers = CastUtils.cast((Map<?, ?>)m.get(Message.PROTOCOL_HEADERS));
+ return new MetadataMap<String, Object>(headers, false, false, true);
+ }
+
+ public static void convertHeaderValuesToString(Map<String, List<Object>> headers, boolean delegateOnly) {
+ if (headers == null) {
+ return;
+ }
+ RuntimeDelegate rd = getOtherRuntimeDelegate();
+ if (rd == null && delegateOnly) {
+ return;
+ }
+ for (Map.Entry<String, List<Object>> entry : headers.entrySet()) {
+ List<Object> values = entry.getValue();
+ for (int i = 0; i < values.size(); i++) {
+ Object value = values.get(i);
+
+ if (value != null && !(value instanceof String)) {
+
+ HeaderDelegate<Object> hd = getHeaderDelegate(rd, value);
+
+ if (hd != null) {
+ value = hd.toString(value);
+ } else if (!delegateOnly) {
+ value = value.toString();
+ }
+
+ try {
+ values.set(i, value);
+ } catch (UnsupportedOperationException ex) {
+ // this may happen if an unmodifiable List was set via Map put
+ List<Object> newList = new ArrayList<>(values);
+ newList.set(i, value);
+ // Won't help if the map is unmodifiable in which case it is a bug anyway
+ headers.put(entry.getKey(), newList);
+ }
+
+ }
+
+ }
+ }
+
+ }
+
+ public static Date getHttpDate(String value) {
+ if (value == null) {
+ return null;
+ }
+ try {
+ return Headers.getHttpDateFormat().parse(value);
+ } catch (ParseException ex) {
+ return null;
+ }
+ }
+
+ public static Locale getLocale(String value) {
+ if (value == null) {
+ return null;
+ }
+ final String language;
+ String locale = null;
+ int index = value.indexOf('-');
+ if (index == 0 || index == value.length() - 1) {
+ throw new IllegalArgumentException("Illegal locale value : " + value);
+ }
+
+ if (index > 0) {
+ language = value.substring(0, index);
+ locale = value.substring(index + 1);
+ } else {
+ language = value;
+ }
+
+ if (locale == null) {
+ return new Locale(language);
+ }
+ return new Locale(language, locale);
+
+ }
+
+ public static int getContentLength(String value) {
+ if (value == null) {
+ return -1;
+ }
+ try {
+ int len = Integer.parseInt(value);
+ return len >= 0 ? len : -1;
+ } catch (Exception ex) {
+ return -1;
+ }
+ }
+
+ public static String getHeaderString(List<String> values) {
+ if (values == null) {
+ return null;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < values.size(); i++) {
+ String value = values.get(i);
+ if (StringUtils.isEmpty(value)) {
+ continue;
+ }
+ sb.append(value);
+ if (i + 1 < values.size()) {
+ sb.append(',');
+ }
+ }
+ return sb.toString();
+ }
+
+ public static boolean isDateRelatedHeader(String headerName) {
+ return HttpHeaders.DATE.equalsIgnoreCase(headerName)
+ || HttpHeaders.IF_MODIFIED_SINCE.equalsIgnoreCase(headerName)
+ || HttpHeaders.IF_UNMODIFIED_SINCE.equalsIgnoreCase(headerName)
+ || HttpHeaders.EXPIRES.equalsIgnoreCase(headerName)
+ || HttpHeaders.LAST_MODIFIED.equalsIgnoreCase(headerName);
+ }
+
+ public static boolean isHttpRequest(Message message) {
+ return message.get(AbstractHTTPDestination.HTTP_REQUEST) != null;
+ }
+
+ public static URI toAbsoluteUri(String relativePath, Message message) {
+ String base = BaseUrlHelper.getBaseURL(
+ (HttpServletRequest)message.get(AbstractHTTPDestination.HTTP_REQUEST));
+ return URI.create(base + relativePath);
+ }
+
+ public static URI toAbsoluteUri(URI u, Message message) {
+ HttpServletRequest request =
+ (HttpServletRequest)message.get(AbstractHTTPDestination.HTTP_REQUEST);
+ boolean absolute = u.isAbsolute();
+ StringBuilder uriBuf = new StringBuilder();
+ if (request != null && (!absolute || isLocalHostOrAnyIpAddress(u, uriBuf, message))) {
+ String serverAndPort = request.getServerName();
+ boolean localAddressUsed = false;
+ if (absolute) {
+ if (ANY_IP_ADDRESS.equals(serverAndPort)) {
+ serverAndPort = request.getLocalAddr();
+ localAddressUsed = true;
+ }
+ if (LOCAL_HOST_IP_ADDRESS.equals(serverAndPort)) {
+ serverAndPort = "localhost";
+ localAddressUsed = true;
+ }
+ }
+
+
+ int port = localAddressUsed ? request.getLocalPort() : request.getServerPort();
+ if (port != DEFAULT_HTTP_PORT) {
+ serverAndPort += ":" + port;
+ }
+ String base = request.getScheme() + "://" + serverAndPort;
+ if (!absolute) {
+ u = URI.create(base + u.toString());
+ } else {
+ int originalPort = u.getPort();
+ String hostValue = uriBuf.toString().contains(ANY_IP_ADDRESS_SCHEME)
+ ? ANY_IP_ADDRESS : LOCAL_HOST_IP_ADDRESS;
+ String replaceValue = originalPort == -1 ? hostValue : hostValue + ":" + originalPort;
+ u = URI.create(u.toString().replace(replaceValue, serverAndPort));
+ }
+ }
+ return u;
+ }
+
+ private static boolean isLocalHostOrAnyIpAddress(URI u, StringBuilder uriStringBuffer, Message m) {
+ String uriString = u.toString();
+ boolean result = uriString.contains(LOCAL_HOST_IP_ADDRESS_SCHEME) && replaceLoopBackAddress(m)
+ || uriString.contains(ANY_IP_ADDRESS_SCHEME);
+ uriStringBuffer.append(uriString);
+ return result;
+ }
+
+ private static boolean replaceLoopBackAddress(Message m) {
+ Object prop = m.getContextualProperty(REPLACE_LOOPBACK_PROPERTY);
+ return prop == null || PropertyUtils.isTrue(prop);
+ }
+
+ public static void resetRequestURI(Message m, String requestURI) {
+ m.remove(REQUEST_PATH_TO_MATCH_SLASH);
+ m.remove(REQUEST_PATH_TO_MATCH);
+ m.put(Message.REQUEST_URI, requestURI);
+ }
+
+
+ public static String getPathToMatch(Message m, boolean addSlash) {
+ String var = addSlash ? REQUEST_PATH_TO_MATCH_SLASH : REQUEST_PATH_TO_MATCH;
+ String pathToMatch = (String)m.get(var);
+ if (pathToMatch != null) {
+ return pathToMatch;
+ }
+ String requestAddress = getProtocolHeader(m, Message.REQUEST_URI, "/");
+ if (m.get(Message.QUERY_STRING) == null) {
+ int index = requestAddress.lastIndexOf('?');
+ if (index > 0 && index < requestAddress.length()) {
+ m.put(Message.QUERY_STRING, requestAddress.substring(index + 1));
+ requestAddress = requestAddress.substring(0, index);
+ }
+ }
+ String baseAddress = getBaseAddress(m);
+ pathToMatch = getPathToMatch(requestAddress, baseAddress, addSlash);
+ m.put(var, pathToMatch);
+ return pathToMatch;
+ }
+
+ public static String getProtocolHeader(Message m, String name, String defaultValue) {
+ return getProtocolHeader(m, name, defaultValue, false);
+ }
+
+ public static String getProtocolHeader(Message m, String name, String defaultValue, boolean setOnMessage) {
+ String value = (String)m.get(name);
+ if (value == null) {
+ value = new HttpHeadersImpl(m).getRequestHeaders().getFirst(name);
+ if (value != null && setOnMessage) {
+ m.put(name, value);
+ }
+ }
+ return value == null ? defaultValue : value;
+ }
+
+ public static String getBaseAddress(Message m) {
+ String endpointAddress = getEndpointAddress(m);
+ try {
+ URI uri = new URI(endpointAddress);
+ String path = uri.getRawPath();
+ String scheme = uri.getScheme();
+ if (scheme != null && !scheme.startsWith(HttpUtils.HTTP_SCHEME)
+ && HttpUtils.isHttpRequest(m)) {
+ path = HttpUtils.toAbsoluteUri(path, m).getRawPath();
+ }
+ return (path == null || path.length() == 0) ? "/" : path;
+ } catch (URISyntaxException ex) {
+ return endpointAddress;
+ }
+ }
+
+ public static String getEndpointAddress(Message m) {
+ String address;
+ Destination d = m.getExchange().getDestination();
+ if (d != null) {
+ if (d instanceof AbstractHTTPDestination) {
+ EndpointInfo ei = ((AbstractHTTPDestination)d).getEndpointInfo();
+ HttpServletRequest request = (HttpServletRequest)m.get(AbstractHTTPDestination.HTTP_REQUEST);
+ Object property = request != null
+ ? request.getAttribute("org.apache.cxf.transport.endpoint.address") : null;
+ address = property != null ? property.toString() : ei.getAddress();
+ } else {
+ address = m.containsKey(Message.BASE_PATH)
+ ? (String)m.get(Message.BASE_PATH) : d.getAddress().getAddress().getValue();
+ }
+ } else {
+ address = (String)m.get(Message.ENDPOINT_ADDRESS);
+ }
+ if (address.startsWith("http") && address.endsWith("//")) {
+ address = address.substring(0, address.length() - 1);
+ }
+ return address;
+ }
+
+ public static void updatePath(Message m, String path) {
+ String baseAddress = getBaseAddress(m);
+ boolean pathSlash = path.startsWith("/");
+ boolean baseSlash = baseAddress.endsWith("/");
+ if (pathSlash && baseSlash) {
+ path = path.substring(1);
+ } else if (!pathSlash && !baseSlash) {
+ path = "/" + path;
+ }
+ m.put(Message.REQUEST_URI, baseAddress + path);
+ m.remove(REQUEST_PATH_TO_MATCH);
+ m.remove(REQUEST_PATH_TO_MATCH_SLASH);
+ }
+
+
+ public static String getPathToMatch(String path, String address, boolean addSlash) {
+
+ int ind = path.indexOf(address);
+ if (ind == -1 && address.equals(path + "/")) {
+ path += "/";
+ ind = 0;
+ }
+ if (ind == 0) {
+ path = path.substring(address.length());
+ }
+ if (addSlash && !path.startsWith("/")) {
+ path = "/" + path;
+ }
+
+ return path;
+ }
+
+ public static String getOriginalAddress(Message m) {
+ Destination d = m.getDestination();
+ return d == null ? "/" : d.getAddress().getAddress().getValue();
+ }
+
+ public static String fromPathSegment(PathSegment ps) {
+ if (PathSegmentImpl.class.isAssignableFrom(ps.getClass())) {
+ return ((PathSegmentImpl)ps).getOriginalPath();
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.append(ps.getPath());
+ for (Map.Entry<String, List<String>> entry : ps.getMatrixParameters().entrySet()) {
+ for (String value : entry.getValue()) {
+ sb.append(';').append(entry.getKey());
+ if (value != null) {
+ sb.append('=').append(value);
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ public static Response.Status getParameterFailureStatus(ParameterType pType) {
+ if (pType == ParameterType.MATRIX || pType == ParameterType.PATH
+ || pType == ParameterType.QUERY) {
+ return Response.Status.NOT_FOUND;
+ }
+ return Response.Status.BAD_REQUEST;
+ }
+
+ public static String getSetEncoding(MediaType mt, MultivaluedMap<String, Object> headers,
+ String defaultEncoding) {
+ String enc = getMediaTypeCharsetParameter(mt);
+ if (enc == null) {
+ return defaultEncoding;
+ }
+ try {
+ "0".getBytes(enc);
+ return enc;
+ } catch (UnsupportedEncodingException ex) {
+ String message = new org.apache.cxf.common.i18n.Message("UNSUPPORTED_ENCODING",
+ BUNDLE, enc, defaultEncoding).toString();
+ LOG.warning(message);
+ headers.putSingle(HttpHeaders.CONTENT_TYPE,
+ JAXRSUtils.mediaTypeToString(mt, CHARSET_PARAMETER)
+ + ';' + CHARSET_PARAMETER + "="
+ + (defaultEncoding == null ? StandardCharsets.UTF_8 : defaultEncoding));
+ }
+ return defaultEncoding;
+ }
+
+ public static String getEncoding(MediaType mt, String defaultEncoding) {
+ String charset = mt == null ? defaultEncoding : getMediaTypeCharsetParameter(mt);
+ return charset == null ? defaultEncoding : charset;
+ }
+
+ public static String getMediaTypeCharsetParameter(MediaType mt) {
+ String charset = mt.getParameters().get(CHARSET_PARAMETER);
+ if (charset != null && charset.startsWith(DOUBLE_QUOTE)
+ && charset.endsWith(DOUBLE_QUOTE) && charset.length() > 1) {
+ charset = charset.substring(1, charset.length() - 1);
+ }
+ return charset;
+ }
+
+ public static URI resolve(UriBuilder baseBuilder, URI uri) {
+ if (!uri.isAbsolute()) {
+ return baseBuilder.build().resolve(uri);
+ }
+ return uri;
+ }
+
+ public static URI relativize(URI base, URI uri) {
+ // quick bail-out
+ if (!(base.isAbsolute()) || !(uri.isAbsolute())) {
+ return uri;
+ }
+ if (base.isOpaque() || uri.isOpaque()) {
+ // Unlikely case of an URN which can't deal with
+ // relative path, such as urn:isbn:0451450523
+ return uri;
+ }
+ // Check for common root
+ URI root = base.resolve("/");
+ if (!(root.equals(uri.resolve("/")))) {
+ // Different protocol/auth/host/port, return as is
+ return uri;
+ }
+
+ // Ignore hostname bits for the following , but add "/" in the beginning
+ // so that in worst case we'll still return "/fred" rather than
+ // "http://example.com/fred".
+ URI baseRel = URI.create("/").resolve(root.relativize(base));
+ URI uriRel = URI.create("/").resolve(root.relativize(uri));
+
+ // Is it same path?
+ if (baseRel.getPath().equals(uriRel.getPath())) {
+ return baseRel.relativize(uriRel);
+ }
+
+ // Direct siblings? (ie. in same folder)
+ URI commonBase = baseRel.resolve("./");
+ if (commonBase.equals(uriRel.resolve("./"))) {
+ return commonBase.relativize(uriRel);
+ }
+
+ // No, then just keep climbing up until we find a common base.
+ URI relative = URI.create("");
+ while (!(uriRel.getPath().startsWith(commonBase.getPath())) && !"/".equals(commonBase.getPath())) {
+ commonBase = commonBase.resolve("../");
+ relative = relative.resolve("../");
+ }
+
+ // Now we can use URI.relativize
+ URI relToCommon = commonBase.relativize(uriRel);
+ // and prepend the needed ../
+ return relative.resolve(relToCommon);
+
+ }
+
+ public static String toHttpLanguage(Locale locale) {
+ return Headers.toHttpLanguage(locale);
+ }
+
+ public static boolean isPayloadEmpty(MultivaluedMap<String, String> headers) {
+ if (headers != null) {
+ String value = headers.getFirst(HttpHeaders.CONTENT_LENGTH);
+ if (value != null) {
+ try {
+ Long len = Long.valueOf(value);
+ return len <= 0;
+ } catch (NumberFormatException ex) {
+ // ignore
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public static <T> T createServletResourceValue(Message m, Class<T> clazz) {
+
+ Object value = null;
+ if (clazz == HttpServletRequest.class) {
+ HttpServletRequest request = (HttpServletRequest)m.get(AbstractHTTPDestination.HTTP_REQUEST);
+ value = request != null ? new HttpServletRequestFilter(request, m) : null;
+ } else if (clazz == HttpServletResponse.class) {
+ HttpServletResponse response = (HttpServletResponse)m.get(AbstractHTTPDestination.HTTP_RESPONSE);
+ value = response != null ? new HttpServletResponseFilter(response, m) : null;
+ } else if (clazz == ServletContext.class) {
+ value = m.get(AbstractHTTPDestination.HTTP_CONTEXT);
+ } else if (clazz == ServletConfig.class) {
+ value = m.get(AbstractHTTPDestination.HTTP_CONFIG);
+ }
+
+ return clazz.cast(value);
+ }
+
+ public static boolean isMethodWithNoRequestContent(String method) {
+ return KNOWN_HTTP_VERBS_WITH_NO_REQUEST_CONTENT.contains(method);
+ }
+
+ public static boolean isMethodWithNoResponseContent(String method) {
+ return KNOWN_HTTP_VERBS_WITH_NO_RESPONSE_CONTENT.contains(method);
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java b/transform/src/patch/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java
new file mode 100644
index 0000000..fb53bd6
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java
@@ -0,0 +1,1044 @@
+/**
+ * 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.jaxrs.utils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.Encoded;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.MatrixParam;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import javax.xml.namespace.QName;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.i18n.BundleUtils;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.feature.Feature;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.helpers.DOMUtils;
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.ext.DefaultMethod;
+import org.apache.cxf.jaxrs.ext.xml.ElementClass;
+import org.apache.cxf.jaxrs.ext.xml.XMLName;
+import org.apache.cxf.jaxrs.lifecycle.PerRequestResourceProvider;
+import org.apache.cxf.jaxrs.lifecycle.ResourceProvider;
+import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
+import org.apache.cxf.jaxrs.model.ClassResourceInfo;
+import org.apache.cxf.jaxrs.model.MethodDispatcher;
+import org.apache.cxf.jaxrs.model.OperationResourceInfo;
+import org.apache.cxf.jaxrs.model.Parameter;
+import org.apache.cxf.jaxrs.model.ParameterType;
+import org.apache.cxf.jaxrs.model.ResourceTypes;
+import org.apache.cxf.jaxrs.model.URITemplate;
+import org.apache.cxf.jaxrs.model.UserOperation;
+import org.apache.cxf.jaxrs.model.UserResource;
+import org.apache.cxf.jaxrs.provider.JAXBElementProvider;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageImpl;
+import org.apache.cxf.resource.ResourceManager;
+import org.apache.cxf.staxutils.StaxUtils;
+
+public final class ResourceUtils {
+
+ private static final Logger LOG = LogUtils.getL7dLogger(ResourceUtils.class);
+ private static final ResourceBundle BUNDLE = BundleUtils.getBundle(ResourceUtils.class);
+ private static final String CLASSPATH_PREFIX = "classpath:";
+ private static final String NOT_RESOURCE_METHOD_MESSAGE_ID = "NOT_RESOURCE_METHOD";
+ private static final String NOT_SUSPENDED_ASYNC_MESSAGE_ID = "NOT_SUSPENDED_ASYNC_METHOD";
+ private static final String NO_VOID_RETURN_ASYNC_MESSAGE_ID = "NO_VOID_RETURN_ASYNC_METHOD";
+ private static final Set<String> SERVER_PROVIDER_CLASS_NAMES;
+ static {
+ SERVER_PROVIDER_CLASS_NAMES = new HashSet<>();
+ SERVER_PROVIDER_CLASS_NAMES.add("javax.ws.rs.ext.MessageBodyWriter");
+ SERVER_PROVIDER_CLASS_NAMES.add("javax.ws.rs.ext.MessageBodyReader");
+ SERVER_PROVIDER_CLASS_NAMES.add("javax.ws.rs.ext.ExceptionMapper");
+ SERVER_PROVIDER_CLASS_NAMES.add("javax.ws.rs.ext.ContextResolver");
+ SERVER_PROVIDER_CLASS_NAMES.add("javax.ws.rs.ext.ReaderInterceptor");
+ SERVER_PROVIDER_CLASS_NAMES.add("javax.ws.rs.ext.WriterInterceptor");
+ SERVER_PROVIDER_CLASS_NAMES.add("javax.ws.rs.ext.ParamConverterProvider");
+ SERVER_PROVIDER_CLASS_NAMES.add("javax.ws.rs.container.ContainerRequestFilter");
+ SERVER_PROVIDER_CLASS_NAMES.add("javax.ws.rs.container.ContainerResponseFilter");
+ SERVER_PROVIDER_CLASS_NAMES.add("javax.ws.rs.container.DynamicFeature");
+ SERVER_PROVIDER_CLASS_NAMES.add("javax.ws.rs.core.Feature");
+ SERVER_PROVIDER_CLASS_NAMES.add("org.apache.cxf.jaxrs.ext.ContextProvider");
+
+ }
+
+ private ResourceUtils() {
+ }
+
+ private static Method[] getDeclaredMethods(final Class<?> c) {
+ return AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
+ @Override
+ public Method[] run() {
+ return c.getDeclaredMethods();
+ }
+ });
+ }
+ public static Method findPostConstructMethod(Class<?> c) {
+ return findPostConstructMethod(c, null);
+ }
+ public static Method findPostConstructMethod(Class<?> c, String name) {
+ if (Object.class == c || null == c) {
+ return null;
+ }
+ for (Method m : getDeclaredMethods(c)) {
+ if (name != null) {
+ if (m.getName().equals(name)) {
+ return m;
+ }
+ } else if (m.getAnnotation(PostConstruct.class) != null) {
+ return m;
+ }
+ }
+ Method m = findPostConstructMethod(c.getSuperclass(), name);
+ if (m != null) {
+ return m;
+ }
+ for (Class<?> i : c.getInterfaces()) {
+ m = findPostConstructMethod(i, name);
+ if (m != null) {
+ return m;
+ }
+ }
+ return null;
+ }
+
+ public static Method findPreDestroyMethod(Class<?> c) {
+ return findPreDestroyMethod(c, null);
+ }
+
+ public static Method findPreDestroyMethod(Class<?> c, String name) {
+ if (Object.class == c || null == c) {
+ return null;
+ }
+ for (Method m : getDeclaredMethods(c)) {
+ if (name != null) {
+ if (m.getName().equals(name)) {
+ return m;
+ }
+ } else if (m.getAnnotation(PreDestroy.class) != null) {
+ return m;
+ }
+ }
+ Method m = findPreDestroyMethod(c.getSuperclass(), name);
+ if (m != null) {
+ return m;
+ }
+ for (Class<?> i : c.getInterfaces()) {
+ m = findPreDestroyMethod(i, name);
+ if (m != null) {
+ return m;
+ }
+ }
+ return null;
+ }
+
+ public static ClassResourceInfo createClassResourceInfo(
+ Map<String, UserResource> resources, UserResource model,
+ Class<?> defaultClass,
+ boolean isRoot, boolean enableStatic,
+ Bus bus) {
+ final boolean isDefaultClass = defaultClass != null;
+ Class<?> sClass = !isDefaultClass ? loadClass(model.getName()) : defaultClass;
+ return createServiceClassResourceInfo(resources, model, sClass, isRoot, enableStatic, bus);
+ }
+
+ public static ClassResourceInfo createServiceClassResourceInfo(
+ Map<String, UserResource> resources, UserResource model,
+ Class<?> sClass, boolean isRoot, boolean enableStatic, Bus bus) {
+ if (model == null) {
+ throw new RuntimeException("Resource class " + sClass.getName() + " has no model info");
+ }
+ ClassResourceInfo cri =
+ new ClassResourceInfo(sClass, sClass, isRoot, enableStatic, true,
+ model.getConsumes(), model.getProduces(), bus);
+ URITemplate t = URITemplate.createTemplate(model.getPath());
+ cri.setURITemplate(t);
+
+ MethodDispatcher md = new MethodDispatcher();
+ Map<String, UserOperation> ops = model.getOperationsAsMap();
+
+ Method defaultMethod = null;
+ Map<String, Method> methodNames = new HashMap<>();
+ for (Method m : cri.getServiceClass().getMethods()) {
+ if (m.getAnnotation(DefaultMethod.class) != null) {
+ // if needed we can also support multiple default methods
+ defaultMethod = m;
+ }
+ methodNames.put(m.getName(), m);
+ }
+
+ for (Map.Entry<String, UserOperation> entry : ops.entrySet()) {
+ UserOperation op = entry.getValue();
+ Method actualMethod = methodNames.get(op.getName());
+ if (actualMethod == null) {
+ actualMethod = defaultMethod;
+ }
+ if (actualMethod == null) {
+ continue;
+ }
+ OperationResourceInfo ori =
+ new OperationResourceInfo(actualMethod, cri, URITemplate.createTemplate(op.getPath()),
+ op.getVerb(), op.getConsumes(), op.getProduces(),
+ op.getParameters(),
+ op.isOneway());
+ String rClassName = actualMethod.getReturnType().getName();
+ if (op.getVerb() == null) {
+ if (resources.containsKey(rClassName)) {
+ ClassResourceInfo subCri = rClassName.equals(model.getName()) ? cri
+ : createServiceClassResourceInfo(resources, resources.get(rClassName),
+ actualMethod.getReturnType(), false, enableStatic, bus);
+ if (subCri != null) {
+ cri.addSubClassResourceInfo(subCri);
+ md.bind(ori, actualMethod);
+ }
+ }
+ } else {
+ md.bind(ori, actualMethod);
+ }
+ }
+
+ cri.setMethodDispatcher(md);
+ return checkMethodDispatcher(cri) ? cri : null;
+
+ }
+
+ public static ClassResourceInfo createClassResourceInfo(final Class<?> rClass,
+ final Class<?> sClass,
+ boolean root,
+ boolean enableStatic) {
+ return createClassResourceInfo(rClass, sClass, root, enableStatic, BusFactory.getThreadDefaultBus());
+
+ }
+
+ public static ClassResourceInfo createClassResourceInfo(final Class<?> rClass,
+ final Class<?> sClass,
+ boolean root,
+ boolean enableStatic,
+ Bus bus) {
+ return createClassResourceInfo(rClass, sClass, null, root, enableStatic, bus);
+ }
+
+ public static ClassResourceInfo createClassResourceInfo(final Class<?> rClass,
+ final Class<?> sClass,
+ ClassResourceInfo parent,
+ boolean root,
+ boolean enableStatic,
+ Bus bus) {
+ return createClassResourceInfo(rClass, sClass, parent, root, enableStatic, bus, null, null);
+ }
+
+ //CHECKSTYLE:OFF
+ public static ClassResourceInfo createClassResourceInfo(final Class<?> rClass,
+ final Class<?> sClass,
+ ClassResourceInfo parent,
+ boolean root,
+ boolean enableStatic,
+ Bus bus,
+ List<MediaType> defaultConsumes,
+ List<MediaType> defaultProduces) {
+ //CHECKSTYLE:ON
+ ClassResourceInfo cri = new ClassResourceInfo(rClass, sClass, root, enableStatic, bus,
+ defaultConsumes, defaultProduces);
+ cri.setParent(parent);
+
+ if (root) {
+ URITemplate t = URITemplate.createTemplate(cri.getPath());
+ cri.setURITemplate(t);
+ }
+
+ evaluateResourceClass(cri, enableStatic);
+ return checkMethodDispatcher(cri) ? cri : null;
+ }
+
+ private static void evaluateResourceClass(ClassResourceInfo cri, boolean enableStatic) {
+ MethodDispatcher md = new MethodDispatcher();
+ Class<?> serviceClass = cri.getServiceClass();
+
+ final Set<Method> annotatedMethods = new HashSet<>();
+
+ for (Method m : serviceClass.getMethods()) {
+ if (!m.isBridge() && !m.isSynthetic()) {
+ //do real methods first
+ Method annotatedMethod = AnnotationUtils.getAnnotatedMethod(serviceClass, m);
+ if (!annotatedMethods.contains(annotatedMethod)) {
+ evaluateResourceMethod(cri, enableStatic, md, m, annotatedMethod);
+ annotatedMethods.add(annotatedMethod);
+ }
+ }
+ }
+ for (Method m : serviceClass.getMethods()) {
+ if (m.isBridge() || m.isSynthetic()) {
+ //if a bridge/synthetic method isn't already mapped to something, go ahead and do it
+ Method annotatedMethod = AnnotationUtils.getAnnotatedMethod(serviceClass, m);
+ if (!annotatedMethods.contains(annotatedMethod)) {
+ evaluateResourceMethod(cri, enableStatic, md, m, annotatedMethod);
+ annotatedMethods.add(annotatedMethod);
+ }
+ }
+ }
+ cri.setMethodDispatcher(md);
+ }
+
+ private static void evaluateResourceMethod(ClassResourceInfo cri, boolean enableStatic, MethodDispatcher md,
+ Method m, Method annotatedMethod) {
+ String httpMethod = AnnotationUtils.getHttpMethodValue(annotatedMethod);
+ Path path = AnnotationUtils.getMethodAnnotation(annotatedMethod, Path.class);
+
+ if (httpMethod != null || path != null) {
+ if (!checkAsyncResponse(annotatedMethod)) {
+ return;
+ }
+
+ md.bind(createOperationInfo(m, annotatedMethod, cri, path, httpMethod), m);
+ if (httpMethod == null) {
+ // subresource locator
+ Class<?> subClass = m.getReturnType();
+ if (subClass == Class.class) {
+ subClass = InjectionUtils.getActualType(m.getGenericReturnType());
+ }
+ if (enableStatic) {
+ ClassResourceInfo subCri = cri.findResource(subClass, subClass);
+ if (subCri == null) {
+ ClassResourceInfo ancestor = getAncestorWithSameServiceClass(cri, subClass);
+ subCri = ancestor != null ? ancestor
+ : createClassResourceInfo(subClass, subClass, cri, false, enableStatic,
+ cri.getBus());
+ }
+
+ if (subCri != null) {
+ cri.addSubClassResourceInfo(subCri);
+ }
+ }
+ }
+ } else {
+ reportInvalidResourceMethod(m, NOT_RESOURCE_METHOD_MESSAGE_ID, Level.FINE);
+ }
+ }
+
+ private static void reportInvalidResourceMethod(Method m, String messageId, Level logLevel) {
+ if (LOG.isLoggable(logLevel)) {
+ LOG.log(logLevel, new org.apache.cxf.common.i18n.Message(messageId,
+ BUNDLE,
+ m.getDeclaringClass().getName(),
+ m.getName()).toString());
+ }
+ }
+
+ private static boolean checkAsyncResponse(Method m) {
+ Class<?>[] types = m.getParameterTypes();
+ for (int i = 0; i < types.length; i++) {
+ if (types[i] == AsyncResponse.class) {
+ if (AnnotationUtils.getAnnotation(m.getParameterAnnotations()[i], Suspended.class) == null) {
+ reportInvalidResourceMethod(m, NOT_SUSPENDED_ASYNC_MESSAGE_ID, Level.FINE);
+ return false;
+ }
+ if (m.getReturnType() == Void.TYPE || m.getReturnType() == Void.class) {
+ return true;
+ }
+ reportInvalidResourceMethod(m, NO_VOID_RETURN_ASYNC_MESSAGE_ID, Level.WARNING);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static ClassResourceInfo getAncestorWithSameServiceClass(ClassResourceInfo parent, Class<?> subClass) {
+ if (parent == null) {
+ return null;
+ }
+ if (parent.getServiceClass() == subClass) {
+ return parent;
+ }
+ return getAncestorWithSameServiceClass(parent.getParent(), subClass);
+ }
+
+ public static Constructor<?> findResourceConstructor(Class<?> resourceClass, boolean perRequest) {
+ List<Constructor<?>> cs = new LinkedList<>();
+ for (Constructor<?> c : resourceClass.getConstructors()) {
+ Class<?>[] params = c.getParameterTypes();
+ Annotation[][] anns = c.getParameterAnnotations();
+ boolean match = true;
+ for (int i = 0; i < params.length; i++) {
+ if (!perRequest) {
+ if (AnnotationUtils.getAnnotation(anns[i], Context.class) == null) {
+ match = false;
+ break;
+ }
+ } else if (!AnnotationUtils.isValidParamAnnotations(anns[i])) {
+ match = false;
+ break;
+ }
+ }
+ if (match) {
+ cs.add(c);
+ }
+ }
+ Collections.sort(cs, new Comparator<Constructor<?>>() {
+
+ public int compare(Constructor<?> c1, Constructor<?> c2) {
+ int p1 = c1.getParameterTypes().length;
+ int p2 = c2.getParameterTypes().length;
+ return p1 > p2 ? -1 : p1 < p2 ? 1 : 0;
+ }
+
+ });
+ return cs.isEmpty() ? null : cs.get(0);
+ }
+
+ public static List<Parameter> getParameters(Method resourceMethod) {
+ Annotation[][] paramAnns = resourceMethod.getParameterAnnotations();
+ if (paramAnns.length == 0) {
+ return CastUtils.cast(Collections.emptyList(), Parameter.class);
+ }
+ Class<?>[] types = resourceMethod.getParameterTypes();
+ List<Parameter> params = new ArrayList<>(paramAnns.length);
+ for (int i = 0; i < paramAnns.length; i++) {
+ Parameter p = getParameter(i, paramAnns[i], types[i]);
+ params.add(p);
+ }
+ return params;
+ }
+
+ //CHECKSTYLE:OFF
+ public static Parameter getParameter(int index, Annotation[] anns, Class<?> type) {
+
+ Context ctx = AnnotationUtils.getAnnotation(anns, Context.class);
+ if (ctx != null) {
+ return new Parameter(ParameterType.CONTEXT, index, null);
+ }
+
+ boolean isEncoded = AnnotationUtils.getAnnotation(anns, Encoded.class) != null;
+
+ BeanParam bp = AnnotationUtils.getAnnotation(anns, BeanParam.class);
+ if (bp != null) {
+ return new Parameter(ParameterType.BEAN, index, null, isEncoded, null);
+ }
+
+ String dValue = AnnotationUtils.getDefaultParameterValue(anns);
+
+ PathParam a = AnnotationUtils.getAnnotation(anns, PathParam.class);
+ if (a != null) {
+ return new Parameter(ParameterType.PATH, index, a.value(), isEncoded, dValue);
+ }
+ QueryParam q = AnnotationUtils.getAnnotation(anns, QueryParam.class);
+ if (q != null) {
+ return new Parameter(ParameterType.QUERY, index, q.value(), isEncoded, dValue);
+ }
+ MatrixParam m = AnnotationUtils.getAnnotation(anns, MatrixParam.class);
+ if (m != null) {
+ return new Parameter(ParameterType.MATRIX, index, m.value(), isEncoded, dValue);
+ }
+
+ FormParam f = AnnotationUtils.getAnnotation(anns, FormParam.class);
+ if (f != null) {
+ return new Parameter(ParameterType.FORM, index, f.value(), isEncoded, dValue);
+ }
+
+ HeaderParam h = AnnotationUtils.getAnnotation(anns, HeaderParam.class);
+ if (h != null) {
+ return new Parameter(ParameterType.HEADER, index, h.value(), isEncoded, dValue);
+ }
+
+ CookieParam c = AnnotationUtils.getAnnotation(anns, CookieParam.class);
+ if (c != null) {
+ return new Parameter(ParameterType.COOKIE, index, c.value(), isEncoded, dValue);
+ }
+
+ return new Parameter(ParameterType.REQUEST_BODY, index, null);
+
+ }
+ //CHECKSTYLE:ON
+
+ private static OperationResourceInfo createOperationInfo(Method m, Method annotatedMethod,
+ ClassResourceInfo cri, Path path, String httpMethod) {
+ OperationResourceInfo ori = new OperationResourceInfo(m, annotatedMethod, cri);
+ URITemplate t = URITemplate.createTemplate(path);
+ ori.setURITemplate(t);
+ ori.setHttpMethod(httpMethod);
+ return ori;
+ }
+
+
+ private static boolean checkMethodDispatcher(ClassResourceInfo cr) {
+ if (cr.getMethodDispatcher().getOperationResourceInfos().isEmpty()) {
+ LOG.warning(new org.apache.cxf.common.i18n.Message("NO_RESOURCE_OP_EXC",
+ BUNDLE,
+ cr.getServiceClass().getName()).toString());
+ return false;
+ }
+ return true;
+ }
+
+
+ private static Class<?> loadClass(String cName) {
+ try {
+ return ClassLoaderUtils.loadClass(cName.trim(), ResourceUtils.class);
+ } catch (ClassNotFoundException ex) {
+ throw new RuntimeException("No class " + cName.trim() + " can be found", ex);
+ }
+ }
+
+
+ public static List<UserResource> getUserResources(String loc, Bus bus) {
+ try (InputStream is = ResourceUtils.getResourceStream(loc, bus)) {
+ if (is == null) {
+ return null;
+ }
+ return getUserResources(is);
+ } catch (Exception ex) {
+ LOG.warning("Problem with processing a user model at " + loc);
+ }
+
+ return null;
+ }
+
+ public static InputStream getResourceStream(String loc, Bus bus) throws IOException {
+ URL url = getResourceURL(loc, bus);
+ return url == null ? null : url.openStream();
+ }
+
+ public static URL getResourceURL(String loc, Bus bus) throws IOException {
+ URL url;
+ if (loc.startsWith(CLASSPATH_PREFIX)) {
+ String path = loc.substring(CLASSPATH_PREFIX.length());
+ url = ResourceUtils.getClasspathResourceURL(path, ResourceUtils.class, bus);
+ } else {
+ try {
+ url = new URL(loc);
+ } catch (Exception ex) {
+ // it can be either a classpath or file resource without a scheme
+ url = ResourceUtils.getClasspathResourceURL(loc, ResourceUtils.class, bus);
+ if (url == null) {
+ File file = new File(loc);
+ if (file.exists()) {
+ url = file.toURI().toURL();
+ }
+ }
+ }
+ }
+ if (url == null) {
+ LOG.warning("No resource " + loc + " is available");
+ }
+ return url;
+ }
+
+ public static InputStream getClasspathResourceStream(String path, Class<?> callingClass, Bus bus) {
+ InputStream is = ClassLoaderUtils.getResourceAsStream(path, callingClass);
+ return is == null ? getResource(path, InputStream.class, bus) : is;
+ }
+
+ public static URL getClasspathResourceURL(String path, Class<?> callingClass, Bus bus) {
+ URL url = ClassLoaderUtils.getResource(path, callingClass);
+ return url == null ? getResource(path, URL.class, bus) : url;
+ }
+
+ public static <T> T getResource(String path, Class<T> resourceClass, Bus bus) {
+ if (bus != null) {
+ ResourceManager rm = bus.getExtension(ResourceManager.class);
+ if (rm != null) {
+ return rm.resolveResource(path, resourceClass);
+ }
+ }
+ return null;
+ }
+
+ public static Properties loadProperties(String propertiesLocation, Bus bus) throws IOException {
+ Properties props = new Properties();
+ try (InputStream is = getResourceStream(propertiesLocation, bus)) {
+ props.load(is);
+ }
+ return props;
+ }
+
+ public static List<UserResource> getUserResources(String loc) {
+ return getUserResources(loc, BusFactory.getThreadDefaultBus());
+ }
+
+ public static List<UserResource> getUserResources(InputStream is) throws Exception {
+ Document doc = StaxUtils.read(new InputStreamReader(is, StandardCharsets.UTF_8));
+ return getResourcesFromElement(doc.getDocumentElement());
+ }
+
+ public static List<UserResource> getResourcesFromElement(Element modelEl) {
+ List<UserResource> resources = new ArrayList<>();
+ List<Element> resourceEls =
+ DOMUtils.findAllElementsByTagNameNS(modelEl,
+ "http://cxf.apache.org/jaxrs", "resource");
+ for (Element e : resourceEls) {
+ resources.add(getResourceFromElement(e));
+ }
+ return resources;
+ }
+
+
+ public static ResourceTypes getAllRequestResponseTypes(List<ClassResourceInfo> cris,
+ boolean jaxbOnly) {
+ return getAllRequestResponseTypes(cris, jaxbOnly, null);
+ }
+
+ public static ResourceTypes getAllRequestResponseTypes(List<ClassResourceInfo> cris,
+ boolean jaxbOnly,
+ MessageBodyWriter<?> jaxbWriter) {
+ ResourceTypes types = new ResourceTypes();
+ for (ClassResourceInfo resource : cris) {
+ getAllTypesForResource(resource, types, jaxbOnly, jaxbWriter);
+ }
+ return types;
+ }
+
+ public static Class<?> getActualJaxbType(Class<?> type, Method resourceMethod, boolean inbound) {
+ ElementClass element = resourceMethod.getAnnotation(ElementClass.class);
+ if (element != null) {
+ Class<?> cls = inbound ? element.request() : element.response();
+ if (cls != Object.class) {
+ return cls;
+ }
+ }
+ return type;
+ }
+
+ private static void getAllTypesForResource(ClassResourceInfo resource,
+ ResourceTypes types,
+ boolean jaxbOnly,
+ MessageBodyWriter<?> jaxbWriter) {
+ Class<?> jaxbElement = null;
+ try {
+ jaxbElement = ClassLoaderUtils.loadClass("javax.xml.bind.JAXBElement", ResourceUtils.class);
+ } catch (final ClassNotFoundException e) {
+ // no-op
+ }
+
+ for (OperationResourceInfo ori : resource.getMethodDispatcher().getOperationResourceInfos()) {
+ Method method = ori.getAnnotatedMethod() == null ? ori.getMethodToInvoke() : ori.getAnnotatedMethod();
+ Class<?> realReturnType = method.getReturnType();
+ Class<?> cls = realReturnType;
+ if (cls == Response.class || ori.isAsync()) {
+ cls = getActualJaxbType(cls, method, false);
+ }
+ Type type = method.getGenericReturnType();
+ if (jaxbOnly) {
+ checkJaxbType(resource.getServiceClass(), cls, realReturnType == Response.class || ori.isAsync()
+ ? cls : type, types, method.getAnnotations(), jaxbWriter, jaxbElement);
+ } else {
+ types.getAllTypes().put(cls, type);
+ }
+
+ for (Parameter pm : ori.getParameters()) {
+ if (pm.getType() == ParameterType.REQUEST_BODY) {
+ Class<?> inType = method.getParameterTypes()[pm.getIndex()];
+ if (inType != AsyncResponse.class) {
+ Type paramType = method.getGenericParameterTypes()[pm.getIndex()];
+ if (jaxbOnly) {
+ checkJaxbType(resource.getServiceClass(), inType, paramType, types,
+ method.getParameterAnnotations()[pm.getIndex()], jaxbWriter, jaxbElement);
+ } else {
+ types.getAllTypes().put(inType, paramType);
+ }
+ }
+ }
+ }
+
+ }
+
+ for (ClassResourceInfo sub : resource.getSubResources()) {
+ if (!isRecursiveSubResource(resource, sub)) {
+ getAllTypesForResource(sub, types, jaxbOnly, jaxbWriter);
+ }
+ }
+ }
+
+ private static boolean isRecursiveSubResource(ClassResourceInfo parent, ClassResourceInfo sub) {
+ if (parent == null) {
+ return false;
+ }
+ if (parent == sub) {
+ return true;
+ }
+ return isRecursiveSubResource(parent.getParent(), sub);
+ }
+
+ private static void checkJaxbType(Class<?> serviceClass,
+ Class<?> type,
+ Type genericType,
+ ResourceTypes types,
+ Annotation[] anns,
+ MessageBodyWriter<?> jaxbWriter,
+ Class<?> jaxbElement) {
+ boolean isCollection = false;
+ if (InjectionUtils.isSupportedCollectionOrArray(type)) {
+ type = InjectionUtils.getActualType(genericType);
+ isCollection = true;
+ }
+ if (type == Object.class && !(genericType instanceof Class)
+ || genericType instanceof TypeVariable) {
+ Type theType = InjectionUtils.processGenericTypeIfNeeded(serviceClass,
+ Object.class,
+ genericType);
+ type = InjectionUtils.getActualType(theType);
+ }
+ if (type == null
+ || InjectionUtils.isPrimitive(type)
+ || (jaxbElement != null && jaxbElement.isAssignableFrom(type))
+ || Response.class.isAssignableFrom(type)
+ || type.isInterface()) {
+ return;
+ }
+
+ MessageBodyWriter<?> writer = jaxbWriter;
+ if (writer == null) {
+ JAXBElementProvider<Object> defaultWriter = new JAXBElementProvider<>();
+ defaultWriter.setMarshallAsJaxbElement(true);
+ defaultWriter.setXmlTypeAsJaxbElementOnly(true);
+ writer = defaultWriter;
+ }
+ if (writer.isWriteable(type, type, anns, MediaType.APPLICATION_XML_TYPE)) {
+ types.getAllTypes().put(type, type);
+ Class<?> genCls = InjectionUtils.getActualType(genericType);
+ if (genCls != type && genCls != null && genCls != Object.class
+ && !InjectionUtils.isSupportedCollectionOrArray(genCls)) {
+ types.getAllTypes().put(genCls, genCls);
+ }
+
+ XMLName name = AnnotationUtils.getAnnotation(anns, XMLName.class);
+ QName qname = name != null ? JAXRSUtils.convertStringToQName(name.value()) : null;
+ if (isCollection) {
+ types.getCollectionMap().put(type, qname);
+ } else {
+ types.getXmlNameMap().put(type, qname);
+ }
+ }
+ }
+
+ private static UserResource getResourceFromElement(Element e) {
+ UserResource resource = new UserResource();
+ resource.setName(e.getAttribute("name"));
+ resource.setPath(e.getAttribute("path"));
+ resource.setConsumes(e.getAttribute("consumes"));
+ resource.setProduces(e.getAttribute("produces"));
+ List<Element> operEls =
+ DOMUtils.findAllElementsByTagNameNS(e,
+ "http://cxf.apache.org/jaxrs", "operation");
+ List<UserOperation> opers = new ArrayList<>(operEls.size());
+ for (Element operEl : operEls) {
+ opers.add(getOperationFromElement(operEl));
+ }
+ resource.setOperations(opers);
+ return resource;
+ }
+
+ private static UserOperation getOperationFromElement(Element e) {
+ UserOperation op = new UserOperation();
+ op.setName(e.getAttribute("name"));
+ op.setVerb(e.getAttribute("verb"));
+ op.setPath(e.getAttribute("path"));
+ op.setOneway(Boolean.parseBoolean(e.getAttribute("oneway")));
+ op.setConsumes(e.getAttribute("consumes"));
+ op.setProduces(e.getAttribute("produces"));
+ List<Element> paramEls =
+ DOMUtils.findAllElementsByTagNameNS(e,
+ "http://cxf.apache.org/jaxrs", "param");
+ List<Parameter> params = new ArrayList<>(paramEls.size());
+ for (int i = 0; i < paramEls.size(); i++) {
+ Element paramEl = paramEls.get(i);
+ Parameter p = new Parameter(paramEl.getAttribute("type"), i, paramEl.getAttribute("name"));
+ p.setEncoded(Boolean.valueOf(paramEl.getAttribute("encoded")));
+ p.setDefaultValue(paramEl.getAttribute("defaultValue"));
+ String pClass = paramEl.getAttribute("class");
+ if (!StringUtils.isEmpty(pClass)) {
+ try {
+ p.setJavaType(ClassLoaderUtils.loadClass(pClass, ResourceUtils.class));
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ params.add(p);
+ }
+ op.setParameters(params);
+ return op;
+ }
+
+ public static Object[] createConstructorArguments(Constructor<?> c,
+ Message m,
+ boolean perRequest) {
+ return createConstructorArguments(c, m, perRequest, null);
+ }
+
+ public static Object[] createConstructorArguments(Constructor<?> c,
+ Message m,
+ boolean perRequest,
+ Map<Class<?>, Object> contextValues) {
+ Class<?>[] params = c.getParameterTypes();
+ Annotation[][] anns = c.getParameterAnnotations();
+ Type[] genericTypes = c.getGenericParameterTypes();
+ return createConstructorArguments(c, m, perRequest, contextValues, params, anns, genericTypes);
+ }
+
+ public static Object[] createConstructorArguments(Constructor<?> c,
+ Message m,
+ boolean perRequest,
+ Map<Class<?>,
+ Object> contextValues,
+ Class<?>[] params,
+ Annotation[][] anns,
+ Type[] genericTypes) {
+ if (m == null) {
+ m = new MessageImpl();
+ }
+ @SuppressWarnings("unchecked")
+ MultivaluedMap<String, String> templateValues =
+ (MultivaluedMap<String, String>)m.get(URITemplate.TEMPLATE_PARAMETERS);
+ Object[] values = new Object[params.length];
+ for (int i = 0; i < params.length; i++) {
+ if (AnnotationUtils.getAnnotation(anns[i], Context.class) != null) {
+ Object contextValue = contextValues != null ? contextValues.get(params[i]) : null;
+ if (contextValue == null) {
+ if (perRequest || InjectionUtils.VALUE_CONTEXTS.contains(params[i].getName())) {
+ values[i] = JAXRSUtils.createContextValue(m, genericTypes[i], params[i]);
+ } else {
+ values[i] = InjectionUtils.createThreadLocalProxy(params[i]);
+ }
+ } else {
+ values[i] = contextValue;
+ }
+ } else {
+ // this branch won't execute for singletons given that the found constructor
+ // is guaranteed to have only Context parameters, if any, for singletons
+ Parameter p = ResourceUtils.getParameter(i, anns[i], params[i]);
+ values[i] = JAXRSUtils.createHttpParameterValue(
+ p, params[i], genericTypes[i], anns[i], m, templateValues, null);
+ }
+ }
+ return values;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static JAXRSServerFactoryBean createApplication(Application app,
+ boolean ignoreAppPath,
+ boolean staticSubresourceResolution,
+ boolean useSingletonResourceProvider,
+ Bus bus) {
+
+ Set<Object> singletons = app.getSingletons();
+ verifySingletons(singletons);
+
+ List<Class<?>> resourceClasses = new ArrayList<>();
+ List<Object> providers = new ArrayList<>();
+ List<Feature> features = new ArrayList<>();
+ Map<Class<?>, ResourceProvider> map = new HashMap<>();
+
+ // Note, app.getClasses() returns a list of per-request classes
+ // or singleton provider classes
+ for (Class<?> cls : app.getClasses()) {
+ if (isValidApplicationClass(cls, singletons)) {
+ if (isValidProvider(cls)) {
+ providers.add(createProviderInstance(cls));
+ } else if (Feature.class.isAssignableFrom(cls)) {
+ features.add(createFeatureInstance((Class<? extends Feature>) cls));
+ } else {
+ resourceClasses.add(cls);
+ if (useSingletonResourceProvider) {
+ map.put(cls, new SingletonResourceProvider(createProviderInstance(cls)));
+ } else {
+ map.put(cls, new PerRequestResourceProvider(cls));
+ }
+ }
+ }
+ }
+
+ // we can get either a provider or resource class here
+ for (Object o : singletons) {
+ if (isValidProvider(o.getClass())) {
+ providers.add(o);
+ } else if (o instanceof Feature) {
+ features.add((Feature) o);
+ } else {
+ resourceClasses.add(o.getClass());
+ map.put(o.getClass(), new SingletonResourceProvider(o));
+ }
+ }
+
+ JAXRSServerFactoryBean bean = new JAXRSServerFactoryBean();
+ if (bus != null) {
+ bean.setBus(bus);
+ }
+
+ String address = "/";
+ if (!ignoreAppPath) {
+ ApplicationPath appPath = locateApplicationPath(app.getClass());
+ if (appPath != null) {
+ address = appPath.value();
+ }
+ }
+ if (!address.startsWith("/")) {
+ address = "/" + address;
+ }
+ bean.setAddress(address);
+ bean.setStaticSubresourceResolution(staticSubresourceResolution);
+ bean.setResourceClasses(resourceClasses);
+ bean.setProviders(providers);
+ bean.getFeatures().addAll(features);
+ for (Map.Entry<Class<?>, ResourceProvider> entry : map.entrySet()) {
+ bean.setResourceProvider(entry.getKey(), entry.getValue());
+ }
+ Map<String, Object> appProps = app.getProperties();
+ if (appProps != null) {
+ bean.getProperties(true).putAll(appProps);
+ }
+ bean.setApplication(app);
+ return bean;
+ }
+
+ public static Object createProviderInstance(Class<?> cls) {
+ try {
+ Constructor<?> c = ResourceUtils.findResourceConstructor(cls, false);
+ if (c != null && c.getParameterTypes().length == 0) {
+ return c.newInstance();
+ }
+ return c;
+ } catch (Throwable ex) {
+ throw new RuntimeException("Provider " + cls.getName() + " can not be created", ex);
+ }
+ }
+
+ public static Feature createFeatureInstance(Class<? extends Feature> cls) {
+ try {
+ Constructor<?> c = ResourceUtils.findResourceConstructor(cls, false);
+
+ if (c == null) {
+ throw new RuntimeException("No valid constructor found for " + cls.getName());
+ }
+
+ return (Feature) c.newInstance();
+ } catch (Throwable ex) {
+ throw new RuntimeException("Feature " + cls.getName() + " can not be created", ex);
+ }
+ }
+
+ private static boolean isValidProvider(Class<?> c) {
+ if (c == null || c == Object.class) {
+ return false;
+ }
+ if (c.getAnnotation(Provider.class) != null) {
+ return true;
+ }
+ for (Class<?> itf : c.getInterfaces()) {
+ if (SERVER_PROVIDER_CLASS_NAMES.contains(itf.getName())) {
+ return true;
+ }
+ }
+ return isValidProvider(c.getSuperclass());
+ }
+
+ private static void verifySingletons(Set<Object> singletons) {
+ if (singletons.isEmpty()) {
+ return;
+ }
+ Set<String> map = new HashSet<>();
+ for (Object s : singletons) {
+ if (map.contains(s.getClass().getName())) {
+ throw new RuntimeException("More than one instance of the same singleton class "
+ + s.getClass().getName() + " is available");
+ }
+ map.add(s.getClass().getName());
+ }
+ }
+
+ public static boolean isValidResourceClass(Class<?> c) {
+ if (c.isInterface() || Modifier.isAbstract(c.getModifiers())) {
+ LOG.info("Ignoring invalid resource class " + c.getName());
+ return false;
+ }
+ return true;
+ }
+
+ public static ApplicationPath locateApplicationPath(Class<?> appClass) {
+ ApplicationPath appPath = appClass.getAnnotation(ApplicationPath.class);
+ if (appPath == null && appClass.getSuperclass() != Application.class) {
+ return locateApplicationPath(appClass.getSuperclass());
+ }
+ return appPath;
+ }
+
+ private static boolean isValidApplicationClass(Class<?> c, Set<Object> singletons) {
+ if (!isValidResourceClass(c)) {
+ return false;
+ }
+ for (Object s : singletons) {
+ if (c == s.getClass()) {
+ LOG.info("Ignoring per-request resource class " + c.getName()
+ + " as it is also registered as singleton");
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/message/MessageUtils.java b/transform/src/patch/java/org/apache/cxf/message/MessageUtils.java
new file mode 100644
index 0000000..e7f7523
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/message/MessageUtils.java
@@ -0,0 +1,261 @@
+/**
+ * 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.message;
+
+import java.lang.reflect.Method;
+import java.net.HttpURLConnection;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+import org.w3c.dom.Node;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.PropertyUtils;
+import org.apache.cxf.service.invoker.MethodDispatcher;
+import org.apache.cxf.service.model.BindingOperationInfo;
+
+
+/**
+ * Holder for utility methods relating to messages.
+ */
+public final class MessageUtils {
+
+ private static final Logger LOG = LogUtils.getL7dLogger(MessageUtils.class);
+
+ /**
+ * Prevents instantiation.
+ */
+ private MessageUtils() {
+ }
+
+ /**
+ * Determine if message is outbound.
+ *
+ * @param message the current Message
+ * @return true if the message direction is outbound
+ */
+ public static boolean isOutbound(Message message) {
+ if (message == null) {
+ return false;
+ }
+
+ Exchange exchange = message.getExchange();
+ return exchange != null
+ && (message == exchange.getOutMessage() || message == exchange.getOutFaultMessage());
+ }
+
+ /**
+ * Determine if message is fault.
+ *
+ * @param message the current Message
+ * @return true if the message is a fault
+ */
+ public static boolean isFault(Message message) {
+ return message != null
+ && message.getExchange() != null
+ && (message == message.getExchange().getInFaultMessage() || message == message.getExchange()
+ .getOutFaultMessage());
+ }
+
+ /**
+ * Determine the fault mode for the underlying (fault) message
+ * (for use on server side only).
+ *
+ * @param message the fault message
+ * @return the FaultMode
+ */
+ public static FaultMode getFaultMode(Message message) {
+ if (message != null
+ && message.getExchange() != null
+ && message == message.getExchange().getOutFaultMessage()) {
+ FaultMode mode = message.get(FaultMode.class);
+ if (null != mode) {
+ return mode;
+ }
+ return FaultMode.RUNTIME_FAULT;
+ }
+ return null;
+ }
+
+ /**
+ * Determine if current messaging role is that of requestor.
+ *
+ * @param message the current Message
+ * @return true if the current messaging role is that of requestor
+ */
+ public static boolean isRequestor(Message message) {
+ if (message != null) {
+ Boolean requestor = (Boolean) message.get(Message.REQUESTOR_ROLE);
+ return requestor != null && requestor;
+ }
+ return false;
+ }
+
+ /**
+ * Determine if the current message is a partial response.
+ *
+ * @param message the current message
+ * @return true if the current messags is a partial response
+ */
+ public static boolean isPartialResponse(Message message) {
+ return message != null && Boolean.TRUE.equals(message.get(Message.PARTIAL_RESPONSE_MESSAGE));
+ }
+
+ /**
+ * Determines if the current message is an empty partial response, which
+ * is a partial response with an empty content.
+ *
+ * @param message the current message
+ * @return true if the current messags is a partial empty response
+ */
+ public static boolean isEmptyPartialResponse(Message message) {
+ return message != null && Boolean.TRUE.equals(message.get(Message.EMPTY_PARTIAL_RESPONSE_MESSAGE));
+ }
+
+ /**
+ * Returns true if a value is either the String "true" (regardless of case) or Boolean.TRUE.
+ * @param value
+ * @return true if value is either the String "true" or Boolean.TRUE
+ * @deprecated replaced by {@link #PropertyUtils#isTrue(Object)}
+ */
+ @Deprecated
+ public static boolean isTrue(Object value) {
+ return PropertyUtils.isTrue(value);
+ }
+
+ public static boolean getContextualBoolean(Message m, String key) {
+ return getContextualBoolean(m, key, false);
+ }
+ public static boolean getContextualBoolean(Message m, String key, boolean defaultValue) {
+ if (m != null) {
+ Object o = m.getContextualProperty(key);
+ if (o != null) {
+ return PropertyUtils.isTrue(o);
+ }
+ }
+ return defaultValue;
+ }
+
+ public static int getContextualInteger(Message m, String key, int defaultValue) {
+ if (m != null) {
+ Object o = m.getContextualProperty(key);
+ if (o instanceof String) {
+ try {
+ int i = Integer.parseInt((String)o);
+ if (i > 0) {
+ return i;
+ }
+ } catch (NumberFormatException ex) {
+ LOG.warning("Incorrect integer value of " + o + " specified for: " + key);
+ }
+ }
+ }
+ return defaultValue;
+ }
+
+ public static Object getContextualProperty(Message m, String propPreferred, String propDefault) {
+ Object prop = null;
+ if (m != null) {
+ prop = m.getContextualProperty(propPreferred);
+ if (prop == null && propDefault != null) {
+ prop = m.getContextualProperty(propDefault);
+ }
+ }
+ return prop;
+ }
+
+ /**
+ * Returns true if the underlying content format is a W3C DOM or a SAAJ message.
+ */
+ public static boolean isDOMPresent(Message m) {
+ return m != null && m.getContent(Node.class) != null;
+ /*
+ for (Class c : m.getContentFormats()) {
+ if (c.equals(Node.class) || "javax.xml.soap.SOAPMessage".equals(c.getName())) {
+ return true;
+ }
+ }
+ return false;
+ */
+ }
+
+ public static Optional<Method> getTargetMethod(Message m) {
+ Method method;
+ BindingOperationInfo bop = m.getExchange().getBindingOperationInfo();
+ if (bop != null) {
+ MethodDispatcher md = (MethodDispatcher) m.getExchange().getService().get(MethodDispatcher.class.getName());
+ method = md.getMethod(bop);
+ } else {
+ // See please JAXRSInInterceptor#RESOURCE_METHOD for the reference
+ method = (Method) m.get("org.apache.cxf.resource.method");
+ }
+ return Optional.ofNullable(method);
+ }
+
+ /**
+ * Gets the response code from the message and tries to deduct one if it
+ * is not set yet.
+ * @param message message to get response code from
+ * @return response code (or deducted value assuming success)
+ */
+ public static int getReponseCodeFromMessage(Message message) {
+ Integer i = (Integer)message.get(Message.RESPONSE_CODE);
+ if (i != null) {
+ return i.intValue();
+ }
+ int code = hasNoResponseContent(message) ? HttpURLConnection.HTTP_ACCEPTED : HttpURLConnection.HTTP_OK;
+ // put the code in the message so that others can get it
+ message.put(Message.RESPONSE_CODE, code);
+ return code;
+ }
+
+ /**
+ * Determines if the current message has no response content.
+ * The message has no response content if either:
+ * - the request is oneway and the current message is no partial
+ * response or an empty partial response.
+ * - the request is not oneway but the current message is an empty partial
+ * response.
+ * @param message
+ * @return
+ */
+ public static boolean hasNoResponseContent(Message message) {
+ final boolean ow = isOneWay(message);
+ final boolean pr = MessageUtils.isPartialResponse(message);
+ final boolean epr = MessageUtils.isEmptyPartialResponse(message);
+
+ //REVISIT may need to provide an option to choose other behavior?
+ // old behavior not suppressing any responses => ow && !pr
+ // suppress empty responses for oneway calls => ow && (!pr || epr)
+ // suppress additionally empty responses for decoupled twoway calls =>
+ return (ow && !pr) || epr;
+ }
+
+ /**
+ * Checks if the message is oneway or not
+ * @param message the message under consideration
+ * @return true if the message has been marked as oneway
+ */
+ public static boolean isOneWay(Message message) {
+ final Exchange ex = message.getExchange();
+ return ex != null && ex.isOneWay();
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/phase/PhaseChainCache.java b/transform/src/patch/java/org/apache/cxf/phase/PhaseChainCache.java
new file mode 100644
index 0000000..ccd12d2
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/phase/PhaseChainCache.java
@@ -0,0 +1,137 @@
+/**
+ * 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.phase;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.SortedSet;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.cxf.common.util.ModCountCopyOnWriteArrayList;
+import org.apache.cxf.interceptor.Interceptor;
+import org.apache.cxf.message.Message;
+
+/**
+ * The PhaseChainCache provides default interceptor chains for SOAP requests
+ * and responses, both from the client and web service side. The list of
+ * phases supplied in the get() methods of this class are defined by default
+ * within org.apache.cxf.phase.PhaseManagerImpl. For an example of this class
+ * in use, check the sourcecode of org.apache.cxf.endpoint.ClientImpl.
+ */
+public final class PhaseChainCache {
+ AtomicReference<ChainHolder> lastData = new AtomicReference<>();
+
+ public PhaseInterceptorChain get(SortedSet<Phase> phaseList,
+ List<Interceptor<? extends Message>> p1) {
+ return getChain(lastData, phaseList, p1);
+ }
+
+ public PhaseInterceptorChain get(SortedSet<Phase> phaseList,
+ List<Interceptor<? extends Message>> p1,
+ List<Interceptor<? extends Message>> p2) {
+ return getChain(lastData, phaseList, p1, p2);
+ }
+ public PhaseInterceptorChain get(SortedSet<Phase> phaseList,
+ List<Interceptor<? extends Message>> p1,
+ List<Interceptor<? extends Message>> p2,
+ List<Interceptor<? extends Message>> p3) {
+ return getChain(lastData, phaseList, p1, p2, p3);
+ }
+ public PhaseInterceptorChain get(SortedSet<Phase> phaseList,
+ List<Interceptor<? extends Message>> p1,
+ List<Interceptor<? extends Message>> p2,
+ List<Interceptor<? extends Message>> p3,
+ List<Interceptor<? extends Message>> p4) {
+ return getChain(lastData, phaseList, p1, p2, p3, p4);
+ }
+ public PhaseInterceptorChain get(SortedSet<Phase> phaseList,
+ List<Interceptor<? extends Message>> p1,
+ List<Interceptor<? extends Message>> p2,
+ List<Interceptor<? extends Message>> p3,
+ List<Interceptor<? extends Message>> p4,
+ List<Interceptor<? extends Message>> p5) {
+ return getChain(lastData, phaseList, p1, p2, p3, p4, p5);
+ }
+
+ @SafeVarargs
+ static PhaseInterceptorChain getChain(AtomicReference<ChainHolder> lastData,
+ SortedSet<Phase> phaseList,
+ List<Interceptor<? extends Message>> ... providers) {
+ ChainHolder last = lastData.get();
+
+ if (last == null
+ || !last.matches(providers)) {
+
+ PhaseInterceptorChain chain = new PhaseInterceptorChain(phaseList);
+ List<ModCountCopyOnWriteArrayList<Interceptor<? extends Message>>> copy
+ = new ArrayList<>(providers.length);
+ for (List<Interceptor<? extends Message>> p : providers) {
+ copy.add(new ModCountCopyOnWriteArrayList<>(p));
+ chain.add(p);
+ }
+ last = new ChainHolder(chain, copy);
+ lastData.set(last);
+ }
+
+
+ return last.chain.cloneChain();
+ }
+
+ private static class ChainHolder {
+ List<ModCountCopyOnWriteArrayList<Interceptor<? extends Message>>> lists;
+ PhaseInterceptorChain chain;
+
+ ChainHolder(PhaseInterceptorChain c,
+ List<ModCountCopyOnWriteArrayList<Interceptor<? extends Message>>> l) {
+ lists = l;
+ chain = c;
+ }
+
+ @SafeVarargs
+ final boolean matches(List<Interceptor<? extends Message>> ... providers) {
+ if (lists.size() == providers.length) {
+ for (int x = 0; x < providers.length; x++) {
+ if (lists.get(x).size() != providers[x].size()) {
+ return false;
+ }
+
+ if (providers[x].getClass() == ModCountCopyOnWriteArrayList.class) {
+ if (((ModCountCopyOnWriteArrayList<?>)providers[x]).getModCount()
+ != lists.get(x).getModCount()) {
+ return false;
+ }
+ } else {
+ ListIterator<Interceptor<? extends Message>> i1 = lists.get(x).listIterator();
+ ListIterator<Interceptor<? extends Message>> i2 = providers[x].listIterator();
+
+ while (i1.hasNext()) {
+ if (i1.next() != i2.next()) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/phase/PhaseInterceptorChain.java b/transform/src/patch/java/org/apache/cxf/phase/PhaseInterceptorChain.java
new file mode 100644
index 0000000..c8a52a8
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/phase/PhaseInterceptorChain.java
@@ -0,0 +1,857 @@
+/**
+ * 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.phase;
+
+import java.lang.ref.WeakReference;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.PropertyUtils;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.continuations.SuspendedInvocationException;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.interceptor.Interceptor;
+import org.apache.cxf.interceptor.InterceptorChain;
+import org.apache.cxf.interceptor.ServiceInvokerInterceptor;
+import org.apache.cxf.logging.FaultListener;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.FaultMode;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageUtils;
+import org.apache.cxf.service.Service;
+import org.apache.cxf.service.model.BindingOperationInfo;
+import org.apache.cxf.service.model.OperationInfo;
+import org.apache.cxf.transport.MessageObserver;
+
+/**
+ * A PhaseInterceptorChain orders Interceptors according to the phase they
+ * participate in and also according to the before & after properties on an
+ * Interceptor.
+ * <p>
+ * A List of phases is supplied to the PhaseInterceptorChain in the constructor.
+ * This class is typically instantiated from the PhaseChainCache class in this
+ * package. Interceptors that are added to the chain are ordered by phase.
+ * Within a phase, interceptors can order themselves. Each PhaseInterceptor
+ * has an ID. PhaseInterceptors can supply a Collection of IDs which they
+ * should run before or after, supplying fine grained ordering.
+ * <p>
+ *
+ */
+public class PhaseInterceptorChain implements InterceptorChain {
+ public static final String PREVIOUS_MESSAGE = PhaseInterceptorChain.class.getName() + ".PREVIOUS_MESSAGE";
+
+ private static final Logger LOG = LogUtils.getL7dLogger(PhaseInterceptorChain.class);
+
+ private static final ThreadLocal<Message> CURRENT_MESSAGE = new ThreadLocal<>();
+
+ private final Map<String, Integer> nameMap;
+ private final Phase[] phases;
+
+ // heads[phase] refers to the first interceptor of the given phase
+ private InterceptorHolder[] heads;
+ // tails[phase] refers to the last interceptor of the given phase
+ private InterceptorHolder[] tails;
+ // hasAfters[phase] indicates that the given phase has already inserted
+ // interceptors that may need to be placed after future to-be-inserted
+ // interceptors. This flag is used to activate ordering of interceptors
+ // when new ones are added to the list for this phase.
+ // Note no hasBefores[] is needed because implementation adds subsequent
+ // interceptors to the end of the list by default.
+ private boolean[] hasAfters;
+
+
+ private State state;
+ private Message pausedMessage;
+ private MessageObserver faultObserver;
+ private PhaseInterceptorIterator iterator;
+ private final boolean isFineLogging;
+
+ // currently one chain for one request/response, use below as signal
+ // to avoid duplicate fault processing on nested calling of
+ // doIntercept(), which will throw same fault multi-times
+ private boolean faultOccurred;
+ private boolean chainReleased;
+
+
+ private PhaseInterceptorChain(PhaseInterceptorChain src) {
+ isFineLogging = LOG.isLoggable(Level.FINE);
+
+ //only used for clone
+ state = State.EXECUTING;
+
+ //immutable, just repoint
+ nameMap = src.nameMap;
+ phases = src.phases;
+
+ int length = phases.length;
+ hasAfters = new boolean[length];
+ System.arraycopy(src.hasAfters, 0, hasAfters, 0, length);
+
+ heads = new InterceptorHolder[length];
+ tails = new InterceptorHolder[length];
+
+ InterceptorHolder last = null;
+ for (int x = 0; x < length; x++) {
+ InterceptorHolder ih = src.heads[x];
+ while (ih != null
+ && ih.phaseIdx == x) {
+ InterceptorHolder ih2 = new InterceptorHolder(ih);
+ ih2.prev = last;
+ if (last != null) {
+ last.next = ih2;
+ }
+ if (heads[x] == null) {
+ heads[x] = ih2;
+ }
+ tails[x] = ih2;
+ last = ih2;
+ ih = ih.next;
+ }
+ }
+ }
+
+ public PhaseInterceptorChain(SortedSet<Phase> ps) {
+ state = State.EXECUTING;
+ isFineLogging = LOG.isLoggable(Level.FINE);
+
+ int numPhases = ps.size();
+ phases = new Phase[numPhases];
+ nameMap = new HashMap<>();
+
+ heads = new InterceptorHolder[numPhases];
+ tails = new InterceptorHolder[numPhases];
+ hasAfters = new boolean[numPhases];
+
+ int idx = 0;
+ for (Phase phase : ps) {
+ phases[idx] = phase;
+ nameMap.put(phase.getName(), idx);
+ ++idx;
+ }
+ }
+
+ public static Message getCurrentMessage() {
+ return CURRENT_MESSAGE.get();
+ }
+
+ public static boolean setCurrentMessage(PhaseInterceptorChain chain, Message m) {
+ if (getCurrentMessage() == m) {
+ return false;
+ }
+ if (chain.iterator.hasPrevious()) {
+ chain.iterator.previous();
+ if (chain.iterator.next() instanceof ServiceInvokerInterceptor) {
+ CURRENT_MESSAGE.set(m);
+ return true;
+ }
+ String error = "Only ServiceInvokerInterceptor can update the current chain message";
+ LOG.warning(error);
+ throw new IllegalStateException(error);
+ }
+ return false;
+
+ }
+
+ public synchronized State getState() {
+ return state;
+ }
+
+ public synchronized void releaseAndAcquireChain() {
+ while (!chainReleased) {
+ try {
+ this.wait();
+ } catch (InterruptedException ex) {
+ // ignore
+ }
+ }
+ chainReleased = false;
+ }
+
+ public synchronized void releaseChain() {
+ this.chainReleased = true;
+ this.notifyAll();
+ }
+
+ public PhaseInterceptorChain cloneChain() {
+ return new PhaseInterceptorChain(this);
+ }
+
+ private void updateIterator() {
+ if (iterator == null) {
+ iterator = new PhaseInterceptorIterator(heads);
+ outputChainToLog(false);
+ }
+ }
+
+ public void add(Collection<Interceptor<? extends Message>> newhandlers) {
+ add(newhandlers, false);
+ }
+
+ public void add(Collection<Interceptor<? extends Message>> newhandlers, boolean force) {
+ if (newhandlers == null) {
+ return;
+ }
+
+ for (Interceptor<? extends Message> handler : newhandlers) {
+ add(handler, force);
+ }
+ }
+
+ public void add(Interceptor<? extends Message> i) {
+ add(i, false);
+ }
+
+ public void add(Interceptor<? extends Message> i, boolean force) {
+ PhaseInterceptor<? extends Message> pi = (PhaseInterceptor<? extends Message>)i;
+
+ String phaseName = pi.getPhase();
+ Integer phase = nameMap.get(phaseName);
+
+ if (phase == null) {
+ LOG.warning("Skipping interceptor " + i.getClass().getName()
+ + ((phaseName == null) ? ": Phase declaration is missing."
+ : ": Phase " + phaseName + " specified does not exist."));
+ } else {
+ if (isFineLogging) {
+ LOG.fine("Adding interceptor " + i + " to phase " + phaseName);
+ }
+
+ insertInterceptor(phase, pi, force);
+ }
+ Collection<PhaseInterceptor<? extends Message>> extras
+ = pi.getAdditionalInterceptors();
+ if (extras != null) {
+ for (PhaseInterceptor<? extends Message> p : extras) {
+ add(p, force);
+ }
+ }
+ }
+
+ public synchronized void pause() {
+ state = State.PAUSED;
+ pausedMessage = CURRENT_MESSAGE.get();
+ }
+ public synchronized void unpause() {
+ if (state == State.PAUSED || state == State.SUSPENDED) {
+ state = State.EXECUTING;
+ pausedMessage = null;
+ }
+ }
+
+ public synchronized void suspend() {
+ state = State.SUSPENDED;
+ pausedMessage = CURRENT_MESSAGE.get();
+ }
+
+ public synchronized void resume() {
+ if (state == State.PAUSED || state == State.SUSPENDED) {
+ state = State.EXECUTING;
+ Message m = pausedMessage;
+ pausedMessage = null;
+ doIntercept(m);
+ }
+ }
+
+ /**
+ * Intercept a message, invoking each phase's handlers in turn.
+ *
+ * @param message the message
+ * @throws Exception
+ */
+ @SuppressWarnings("unchecked")
+ public synchronized boolean doIntercept(Message message) {
+ updateIterator();
+
+ Message oldMessage = CURRENT_MESSAGE.get();
+ try {
+ CURRENT_MESSAGE.set(message);
+ if (oldMessage != null
+ && !message.containsKey(PREVIOUS_MESSAGE)
+ && message != oldMessage
+ && message.getExchange() != oldMessage.getExchange()) {
+ message.put(PREVIOUS_MESSAGE, new WeakReference<Message>(oldMessage));
+ }
+ while (state == State.EXECUTING && iterator.hasNext()) {
+ try {
+ Interceptor<Message> currentInterceptor = (Interceptor<Message>)iterator.next();
+ if (isFineLogging) {
+ LOG.fine("Invoking handleMessage on interceptor " + currentInterceptor);
+ }
+ //System.out.println("-----------" + currentInterceptor);
+ currentInterceptor.handleMessage(message);
+ if (state == State.SUSPENDED) {
+ // throw the exception to make sure thread exit without interrupt
+ throw new SuspendedInvocationException();
+ }
+
+ } catch (SuspendedInvocationException ex) {
+
+ // Moving the chain iterator to the previous interceptor is needed
+ // for the invocation to be resumed from the same interceptor which
+ // suspended the invocation.
+ // If "suspend.chain.on.current.interceptor" is set to true then
+ // the chain will be resumed from the interceptor which follows
+ // the interceptor which suspended the invocation.
+ Object suspendProp = message.remove("suspend.chain.on.current.interceptor");
+ if ((suspendProp == null || PropertyUtils.isFalse(suspendProp))
+ && iterator.hasPrevious()) {
+ iterator.previous();
+ }
+ pause();
+ throw ex;
+ } catch (RuntimeException ex) {
+ if (!faultOccurred) {
+ faultOccurred = true;
+ wrapExceptionAsFault(message, ex);
+ }
+ state = State.ABORTED;
+ }
+ }
+ if (state == State.EXECUTING) {
+ state = State.COMPLETE;
+ }
+ return state == State.COMPLETE;
+ } finally {
+ CURRENT_MESSAGE.set(oldMessage);
+ }
+ }
+
+ private void wrapExceptionAsFault(Message message, RuntimeException ex) {
+ String description = getServiceInfo(message);
+
+ message.setContent(Exception.class, ex);
+ unwind(message);
+ Exception ex2 = message.getContent(Exception.class);
+ if (ex2 == null) {
+ ex2 = ex;
+ }
+
+ FaultListener flogger = (FaultListener)
+ message.getContextualProperty(FaultListener.class.getName());
+ boolean useDefaultLogging = true;
+ if (flogger != null) {
+ useDefaultLogging = flogger.faultOccurred(ex2, description, message);
+ }
+ if (useDefaultLogging) {
+ doDefaultLogging(message, ex2, description);
+ }
+
+ if (message.getExchange() != null && message.getContent(Exception.class) != null) {
+ message.getExchange().put(Exception.class, ex2);
+ }
+
+ if (faultObserver != null && !isOneWay(message)) {
+ // CXF-5629. when exchange is one way and robust, it becomes req-resp in order to
+ // send the fault
+ message.getExchange().setOneWay(false);
+ faultObserver.onMessage(message);
+ }
+ }
+
+ private String getServiceInfo(Message message) {
+ StringBuilder description = new StringBuilder();
+ if (message.getExchange() != null) {
+ Exchange exchange = message.getExchange();
+ Service service = exchange.getService();
+ if (service != null) {
+ description.append('\'');
+ description.append(service.getName());
+ BindingOperationInfo boi = exchange.getBindingOperationInfo();
+ OperationInfo opInfo = boi != null ? boi.getOperationInfo() : null;
+ if (opInfo != null) {
+ description.append('#').append(opInfo.getName());
+ }
+ description.append("\' ");
+ }
+ }
+ return description.toString();
+ }
+
+ private void doDefaultLogging(Message message, Exception ex, String description) {
+ FaultMode mode = message.get(FaultMode.class);
+ if (mode == FaultMode.CHECKED_APPLICATION_FAULT) {
+ if (isFineLogging) {
+ LogUtils.log(LOG, Level.FINE,
+ "Application " + description
+ + "has thrown exception, unwinding now", ex);
+ } else if (LOG.isLoggable(Level.INFO)) {
+ Throwable t = ex;
+ if (ex instanceof Fault
+ && ex.getCause() != null) {
+ t = ex.getCause();
+ }
+
+ LogUtils.log(LOG, Level.INFO,
+ "Application " + description
+ + "has thrown exception, unwinding now: "
+ + t.getClass().getName()
+ + ": " + ex.getMessage());
+ }
+ } else if (LOG.isLoggable(Level.WARNING)) {
+ if (mode == FaultMode.UNCHECKED_APPLICATION_FAULT) {
+ LogUtils.log(LOG, Level.WARNING,
+ "Application " + description
+ + "has thrown exception, unwinding now", ex);
+ } else {
+ LogUtils.log(LOG, Level.WARNING,
+ "Interceptor for " + description
+ + "has thrown exception, unwinding now", ex);
+ }
+ }
+ }
+
+ private boolean isOneWay(Message message) {
+ return (message.getExchange() != null) && message.getExchange().isOneWay() && !isRobustOneWay(message);
+ }
+
+ private boolean isRobustOneWay(Message message) {
+ return MessageUtils.getContextualBoolean(message, Message.ROBUST_ONEWAY, false);
+ }
+
+ /**
+ * Intercept a message, invoking each phase's handlers in turn,
+ * starting after the specified interceptor.
+ *
+ * @param message the message
+ * @param startingAfterInterceptorID the id of the interceptor
+ * @throws Exception
+ */
+ public synchronized boolean doInterceptStartingAfter(Message message,
+ String startingAfterInterceptorID) {
+ updateIterator();
+ while (state == State.EXECUTING && iterator.hasNext()) {
+ PhaseInterceptor<? extends Message> currentInterceptor
+ = (PhaseInterceptor<? extends Message>)iterator.next();
+ if (currentInterceptor.getId().equals(startingAfterInterceptorID)) {
+ break;
+ }
+ }
+ return doIntercept(message);
+ }
+
+ /**
+ * Intercept a message, invoking each phase's handlers in turn,
+ * starting at the specified interceptor.
+ *
+ * @param message the message
+ * @param startingAtInterceptorID the id of the interceptor
+ * @throws Exception
+ */
+ public synchronized boolean doInterceptStartingAt(Message message,
+ String startingAtInterceptorID) {
+ updateIterator();
+ while (state == State.EXECUTING && iterator.hasNext()) {
+ PhaseInterceptor<? extends Message> currentInterceptor
+ = (PhaseInterceptor<? extends Message>)iterator.next();
+ if (currentInterceptor.getId().equals(startingAtInterceptorID)) {
+ iterator.previous();
+ break;
+ }
+ }
+ return doIntercept(message);
+ }
+
+ public synchronized void reset() {
+ updateIterator();
+ if (state == State.COMPLETE) {
+ state = State.EXECUTING;
+ iterator.reset();
+ } else {
+ iterator.reset();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public void unwind(Message message) {
+ while (iterator.hasPrevious()) {
+ Interceptor<Message> currentInterceptor = (Interceptor<Message>)iterator.previous();
+ if (isFineLogging) {
+ LOG.fine("Invoking handleFault on interceptor " + currentInterceptor);
+ }
+ try {
+ currentInterceptor.handleFault(message);
+ } catch (RuntimeException e) {
+ LOG.log(Level.WARNING, "Exception in handleFault on interceptor " + currentInterceptor, e);
+ throw e;
+ } catch (Exception e) {
+ LOG.log(Level.WARNING, "Exception in handleFault on interceptor " + currentInterceptor, e);
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ public void remove(Interceptor<? extends Message> i) {
+ PhaseInterceptorIterator it = new PhaseInterceptorIterator(heads);
+ while (it.hasNext()) {
+ InterceptorHolder holder = it.nextInterceptorHolder();
+ if (holder.interceptor == i) {
+ remove(holder);
+ return;
+ }
+ }
+ }
+
+ public synchronized void abort() {
+ this.state = InterceptorChain.State.ABORTED;
+ }
+
+ public Iterator<Interceptor<? extends Message>> iterator() {
+ return getIterator();
+ }
+ public ListIterator<Interceptor<? extends Message>> getIterator() {
+ return new PhaseInterceptorIterator(heads);
+ }
+
+ private void remove(InterceptorHolder i) {
+ if (i.prev != null) {
+ i.prev.next = i.next;
+ }
+ if (i.next != null) {
+ i.next.prev = i.prev;
+ }
+ int ph = i.phaseIdx;
+ if (heads[ph] == i) {
+ if (i.next != null
+ && i.next.phaseIdx == ph) {
+ heads[ph] = i.next;
+ } else {
+ heads[ph] = null;
+ tails[ph] = null;
+ }
+ }
+ if (tails[ph] == i) {
+ if (i.prev != null
+ && i.prev.phaseIdx == ph) {
+ tails[ph] = i.prev;
+ } else {
+ heads[ph] = null;
+ tails[ph] = null;
+ }
+ }
+ }
+
+ private void insertInterceptor(int phase, PhaseInterceptor<? extends Message> interc, boolean force) {
+ InterceptorHolder ih = new InterceptorHolder(interc, phase);
+ if (heads[phase] == null) {
+ // no interceptors yet in this phase
+ heads[phase] = ih;
+ tails[phase] = ih;
+ hasAfters[phase] = !interc.getAfter().isEmpty();
+
+ int idx = phase - 1;
+ while (idx >= 0) {
+ if (tails[idx] != null) {
+ break;
+ }
+ --idx;
+ }
+ if (idx >= 0) {
+ //found something before us, in an earlier phase
+ ih.prev = tails[idx];
+ ih.next = tails[idx].next;
+ if (ih.next != null) {
+ ih.next.prev = ih;
+ }
+ tails[idx].next = ih;
+ } else {
+ //did not find something before us, try after
+ idx = phase + 1;
+ while (idx < heads.length) {
+ if (heads[idx] != null) {
+ break;
+ }
+ ++idx;
+ }
+
+ if (idx != heads.length) {
+ //found something after us
+ ih.next = heads[idx];
+ heads[idx].prev = ih;
+ }
+ }
+ } else { // this phase already has interceptors attached
+
+ // list of interceptors that the new interceptor should precede
+ Set<String> beforeList = interc.getBefore();
+
+ // list of interceptors that the new interceptor should be after
+ Set<String> afterList = interc.getAfter();
+
+ // firstBefore will hold the first interceptor of a given phase
+ // that the interceptor to be added must precede
+ InterceptorHolder firstBefore = null;
+
+ // lastAfter will hold the last interceptor of a given phase
+ // that the interceptor to be added must come after
+ InterceptorHolder lastAfter = null;
+
+ String id = interc.getId();
+ if (hasAfters[phase] || !beforeList.isEmpty()) {
+
+ InterceptorHolder ih2 = heads[phase];
+ while (ih2 != tails[phase].next) {
+ PhaseInterceptor<? extends Message> cmp = ih2.interceptor;
+ String cmpId = cmp.getId();
+ if (cmpId != null && firstBefore == null
+ && (beforeList.contains(cmpId)
+ || cmp.getAfter().contains(id))) {
+ firstBefore = ih2;
+ }
+ if (cmp.getBefore().contains(id)
+ || (cmpId != null && afterList.contains(cmpId))) {
+ lastAfter = ih2;
+ }
+ if (!force && cmpId.equals(id)) {
+ // interceptor is already in chain
+ return;
+ }
+ ih2 = ih2.next;
+ }
+ if (lastAfter == null && beforeList.contains("*")) {
+ firstBefore = heads[phase];
+ }
+
+ } else if (!force) {
+ // skip interceptor if already in chain
+ InterceptorHolder ih2 = heads[phase];
+ while (ih2 != tails[phase].next) {
+ if (ih2.interceptor.getId().equals(id)) {
+ return;
+ }
+ ih2 = ih2.next;
+ }
+
+ }
+ hasAfters[phase] |= !afterList.isEmpty();
+
+ if (firstBefore == null
+ && lastAfter == null
+ && !beforeList.isEmpty()
+ && afterList.isEmpty()) {
+ //if this interceptor has stuff it MUST be before,
+ //but nothing it must be after, just
+ //stick it at the beginning
+ firstBefore = heads[phase];
+ }
+
+ if (firstBefore == null) {
+ //just add new interceptor at the end
+ ih.prev = tails[phase];
+ ih.next = tails[phase].next;
+ tails[phase].next = ih;
+
+ if (ih.next != null) {
+ ih.next.prev = ih;
+ }
+ tails[phase] = ih;
+ } else {
+ ih.prev = firstBefore.prev;
+ if (ih.prev != null) {
+ ih.prev.next = ih;
+ }
+ ih.next = firstBefore;
+ firstBefore.prev = ih;
+
+ if (heads[phase] == firstBefore) {
+ heads[phase] = ih;
+ }
+ }
+ }
+ if (iterator != null) {
+ outputChainToLog(true);
+ }
+ }
+
+ public String toString() {
+ return toString("");
+ }
+ private String toString(String message) {
+ StringBuilder chain = new StringBuilder(128);
+
+ chain.append("Chain ")
+ .append(super.toString())
+ .append(message)
+ .append(". Current flow:\n");
+
+ for (int x = 0; x < phases.length; x++) {
+ if (heads[x] != null) {
+ chain.append(" ");
+ printPhase(x, chain);
+ }
+ }
+ return chain.toString();
+ }
+ private void printPhase(int ph, StringBuilder chain) {
+
+ chain.append(phases[ph].getName())
+ .append(" [");
+ InterceptorHolder i = heads[ph];
+ boolean first = true;
+ while (i != tails[ph].next) {
+ if (first) {
+ first = false;
+ } else {
+ chain.append(", ");
+ }
+ String nm = i.interceptor.getClass().getSimpleName();
+ if (StringUtils.isEmpty(nm)) {
+ nm = i.interceptor.getId();
+ }
+ chain.append(nm);
+ i = i.next;
+ }
+ chain.append("]\n");
+ }
+
+ private void outputChainToLog(boolean modified) {
+ if (isFineLogging) {
+ if (modified) {
+ LOG.fine(toString(" was modified"));
+ } else {
+ LOG.fine(toString(" was created"));
+ }
+ }
+ }
+
+ public MessageObserver getFaultObserver() {
+ return faultObserver;
+ }
+
+ public void setFaultObserver(MessageObserver faultObserver) {
+ this.faultObserver = faultObserver;
+ }
+
+ static final class PhaseInterceptorIterator implements ListIterator<Interceptor<? extends Message>> {
+ InterceptorHolder[] heads;
+ InterceptorHolder prev;
+ InterceptorHolder first;
+
+ PhaseInterceptorIterator(InterceptorHolder[] h) {
+ heads = h;
+ first = findFirst();
+ }
+
+ public void reset() {
+ prev = null;
+ first = findFirst();
+ }
+
+ private InterceptorHolder findFirst() {
+ for (int x = 0; x < heads.length; x++) {
+ if (heads[x] != null) {
+ return heads[x];
+ }
+ }
+ return null;
+ }
+
+
+ public boolean hasNext() {
+ if (prev == null) {
+ return first != null;
+ }
+ return prev.next != null;
+ }
+
+ public Interceptor<? extends Message> next() {
+ if (prev == null) {
+ if (first == null) {
+ throw new NoSuchElementException();
+ }
+ prev = first;
+ } else {
+ if (prev.next == null) {
+ throw new NoSuchElementException();
+ }
+ prev = prev.next;
+ }
+ return prev.interceptor;
+ }
+ public InterceptorHolder nextInterceptorHolder() {
+ if (prev == null) {
+ if (first == null) {
+ throw new NoSuchElementException();
+ }
+ prev = first;
+ } else {
+ if (prev.next == null) {
+ throw new NoSuchElementException();
+ }
+ prev = prev.next;
+ }
+ return prev;
+ }
+
+ public boolean hasPrevious() {
+ return prev != null;
+ }
+ public Interceptor<? extends Message> previous() {
+ if (prev == null) {
+ throw new NoSuchElementException();
+ }
+ InterceptorHolder tmp = prev;
+ prev = prev.prev;
+ return tmp.interceptor;
+ }
+
+ public int nextIndex() {
+ throw new UnsupportedOperationException();
+ }
+ public int previousIndex() {
+ throw new UnsupportedOperationException();
+ }
+ public void add(Interceptor<? extends Message> o) {
+ throw new UnsupportedOperationException();
+ }
+ public void set(Interceptor<? extends Message> o) {
+ throw new UnsupportedOperationException();
+ }
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+
+ static final class InterceptorHolder {
+ PhaseInterceptor<? extends Message> interceptor;
+ InterceptorHolder next;
+ InterceptorHolder prev;
+ int phaseIdx;
+
+ InterceptorHolder(PhaseInterceptor<? extends Message> i, int p) {
+ interceptor = i;
+ phaseIdx = p;
+ }
+ InterceptorHolder(InterceptorHolder p) {
+ interceptor = p.interceptor;
+ phaseIdx = p.phaseIdx;
+ }
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/service/factory/FactoryBeanListener.java b/transform/src/patch/java/org/apache/cxf/service/factory/FactoryBeanListener.java
new file mode 100644
index 0000000..687f211
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/service/factory/FactoryBeanListener.java
@@ -0,0 +1,145 @@
+/**
+ * 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.service.factory;
+
+/**
+ *
+ */
+public interface FactoryBeanListener {
+ enum Event {
+ /**
+ * Event fired at the very start of processing. No parameters. Useful
+ * for setting up any state the listener may need to maintain.
+ */
+ START_CREATE,
+
+ /**
+ * Event fired at the very end of processing. One parameter is passed
+ * in which is the Service object that was created.
+ */
+ END_CREATE,
+
+ /**
+ * Called at the start of processing when it detects that the service
+ * is to be created based on a wsdl contract. One String parameter
+ * of the URL of the wsdl.
+ */
+ CREATE_FROM_WSDL,
+
+ /**
+ * Called at the start of processing when it detects that the service
+ * is to be created based on a Java class. One Class<?> parameter
+ * of the class that is being analyzed.
+ */
+ CREATE_FROM_CLASS,
+
+ /**
+ * Called after the wsdl is loaded/parsed. Single parameter of the
+ * WSS4J Definition of the WSDL.
+ */
+ WSDL_LOADED,
+
+ /**
+ * Called after the Service is set into the Factory after which the getService()
+ * call will return a valid value. One parameter of the Service object.
+ */
+ SERVICE_SET,
+
+
+ /**
+ * OperationInfo, Method
+ */
+ INTERFACE_OPERATION_BOUND,
+
+ /**
+ * OperationInfo, Method, MessageInfo
+ */
+ OPERATIONINFO_IN_MESSAGE_SET,
+ OPERATIONINFO_OUT_MESSAGE_SET,
+
+ /**
+ * OperationInfo, Class<? extends Throwable>, FaultInfo
+ */
+ OPERATIONINFO_FAULT,
+
+ /**
+ * InterfaceInfo, Class<?>
+ */
+ INTERFACE_CREATED,
+
+ /**
+ * DataBinding
+ */
+ DATABINDING_INITIALIZED,
+
+ /**
+ * EndpointInfo, Endpoint, Class
+ */
+ ENDPOINT_CREATED,
+
+ /**
+ * Server, targetObject, Class
+ */
+ PRE_SERVER_CREATE,
+
+ /**
+ * Server, targetObject, Class
+ */
+ SERVER_CREATED,
+
+ /**
+ * BindingInfo, BindingOperationInfo, Implementation Method
+ */
+ BINDING_OPERATION_CREATED,
+
+ /**
+ * BindingInfo
+ */
+ BINDING_CREATED,
+
+ /**
+ * Endpoint
+ */
+ PRE_CLIENT_CREATE,
+
+ /**
+ * Endpoint, Client
+ */
+ CLIENT_CREATED,
+
+ /**
+ * EndpointInfo, Endpoint, SEI Class, Class
+ */
+ ENDPOINT_SELECTED,
+
+ /**
+ * EndpointInfo
+ */
+ ENDPOINTINFO_CREATED,
+
+ /**
+ * Class[], InvokationHandler, Proxy
+ */
+ PROXY_CREATED,
+ }
+
+
+ void handleEvent(Event ev, AbstractServiceFactoryBean factory, Object ... args);
+}
diff --git a/transform/src/patch/java/org/apache/cxf/service/invoker/FactoryInvoker.java b/transform/src/patch/java/org/apache/cxf/service/invoker/FactoryInvoker.java
new file mode 100644
index 0000000..465046b
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/service/invoker/FactoryInvoker.java
@@ -0,0 +1,70 @@
+/**
+ * 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.service.invoker;
+
+import java.util.ResourceBundle;
+
+import org.apache.cxf.common.i18n.BundleUtils;
+import org.apache.cxf.common.i18n.Message;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.message.Exchange;
+
+/**
+ * This invoker implementation calls a Factory to create the service object.
+ *
+ */
+public class FactoryInvoker extends AbstractInvoker {
+ static final ResourceBundle BUNDLE = BundleUtils.getBundle(FactoryInvoker.class);
+
+ protected Factory factory;
+
+ /**
+ * Create a FactoryInvoker object.
+ *
+ * @param factory the factory used to create service object.
+ */
+ public FactoryInvoker(Factory factory) {
+ this.factory = factory;
+ }
+ public FactoryInvoker() {
+ }
+ public void setFactory(Factory f) {
+ this.factory = f;
+ }
+
+ public Object getServiceObject(Exchange ex) {
+ try {
+ return factory.create(ex);
+ } catch (Fault e) {
+ throw e;
+ } catch (Throwable e) {
+ throw new Fault(new Message("CREATE_SERVICE_OBJECT_EXC", BUNDLE), e);
+ }
+ }
+
+ @Override
+ public void releaseServiceObject(final Exchange ex, Object obj) {
+ factory.release(ex, obj);
+ }
+
+ public boolean isSingletonFactory() {
+ return factory instanceof SingletonFactory;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/service/model/AbstractPropertiesHolder.java b/transform/src/patch/java/org/apache/cxf/service/model/AbstractPropertiesHolder.java
new file mode 100644
index 0000000..d80e02d
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/service/model/AbstractPropertiesHolder.java
@@ -0,0 +1,271 @@
+/**
+ * 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.service.model;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.xml.namespace.QName;
+
+public abstract class AbstractPropertiesHolder implements Extensible {
+ private AbstractPropertiesHolder delegate;
+ private boolean delegateProperties;
+
+ private AtomicReference<Map<String, Object>> propertyMap = new AtomicReference<>();
+ private AtomicReference<Object[]> extensors = new AtomicReference<>();
+ private Map<QName, Object> extensionAttributes;
+ private String documentation;
+
+
+ public final void setDelegate(AbstractPropertiesHolder p, boolean props) {
+ delegate = p;
+ delegateProperties = props;
+ if (delegate == null) {
+ return;
+ }
+ if (documentation != null) {
+ delegate.setDocumentation(documentation);
+ documentation = null;
+ }
+ if (extensionAttributes != null) {
+ delegate.setExtensionAttributes(extensionAttributes);
+ extensionAttributes = null;
+ }
+ if (extensors.get() != null) {
+ for (Object el : extensors.get()) {
+ delegate.addExtensor(el);
+ }
+ extensors.set(null);
+ }
+ if (delegateProperties && propertyMap.get() != null) {
+ for (Map.Entry<String, Object> p2 : propertyMap.get().entrySet()) {
+ delegate.setProperty(p2.getKey(), p2.getValue());
+ }
+ propertyMap.set(null);
+ }
+ }
+
+ public String getDocumentation() {
+ if (delegate != null) {
+ return delegate.getDocumentation();
+ }
+ return documentation;
+ }
+ public void setDocumentation(String s) {
+ if (delegate != null) {
+ delegate.setDocumentation(s);
+ } else {
+ documentation = s;
+ }
+ }
+ public Map<String, Object> getProperties() {
+ if (delegate != null && delegateProperties) {
+ return delegate.getProperties();
+ }
+ return propertyMap.get();
+ }
+ public Object getProperty(String name) {
+ if (delegate != null && delegateProperties) {
+ return delegate.getProperty(name);
+ }
+ if (null == propertyMap.get()) {
+ return null;
+ }
+ return propertyMap.get().get(name);
+ }
+ public Object removeProperty(String name) {
+ if (delegate != null && delegateProperties) {
+ delegate.removeProperty(name);
+ }
+ if (null == propertyMap.get()) {
+ return null;
+ }
+ return propertyMap.get().remove(name);
+ }
+
+ public <T> T getProperty(String name, Class<T> cls) {
+ return cls.cast(getProperty(name));
+ }
+ public boolean hasProperty(String name) {
+ if (delegate != null && delegateProperties) {
+ return delegate.hasProperty(name);
+ }
+ Map<String, Object> map = propertyMap.get();
+ if (map != null) {
+ return map.containsKey(name);
+ }
+ return false;
+ }
+
+ public void setProperty(String name, Object v) {
+ if (delegate != null && delegateProperties) {
+ delegate.setProperty(name, v);
+ return;
+ }
+ if (null == propertyMap.get()) {
+ propertyMap.compareAndSet(null, new ConcurrentHashMap<>(4, 0.75f, 2));
+ }
+ if (v == null) {
+ propertyMap.get().remove(name);
+ } else {
+ propertyMap.get().put(name, v);
+ }
+ }
+
+ public boolean containsExtensor(Object el) {
+ if (delegate != null) {
+ return delegate.containsExtensor(el);
+ }
+
+ Object[] exts = extensors.get();
+ if (exts != null) {
+ for (Object o : exts) {
+ if (o == el) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ public void addExtensor(Object el) {
+ if (delegate != null) {
+ delegate.addExtensor(el);
+ return;
+ }
+ Object[] exts = extensors.get();
+ Object[] exts2;
+ if (exts == null) {
+ exts2 = new Object[1];
+ } else {
+ exts2 = new Object[exts.length + 1];
+ System.arraycopy(exts, 0, exts2, 0, exts.length);
+ }
+ exts2[exts2.length - 1] = el;
+ if (!extensors.compareAndSet(exts, exts2)) {
+ //keep trying
+ addExtensor(el);
+ }
+ }
+
+ public <T> T getExtensor(Class<T> cls) {
+ if (delegate != null) {
+ return delegate.getExtensor(cls);
+ }
+ Object[] exts = extensors.get();
+ if (exts == null) {
+ return null;
+ }
+ for (int x = 0; x < exts.length; x++) {
+ if (cls.isInstance(exts[x])) {
+ return cls.cast(exts[x]);
+ }
+ }
+ return null;
+ }
+ public <T> List<T> getExtensors(Class<T> cls) {
+ if (delegate != null) {
+ return delegate.getExtensors(cls);
+ }
+
+ Object[] exts = extensors.get();
+ if (exts == null) {
+ return null;
+ }
+ List<T> list = new ArrayList<>(exts.length);
+ for (int x = 0; x < exts.length; x++) {
+ if (cls.isInstance(exts[x])) {
+ list.add(cls.cast(exts[x]));
+ }
+ }
+ return list;
+ }
+
+ public AtomicReference<Object[]> getExtensors() {
+ if (delegate != null) {
+ return delegate.getExtensors();
+ }
+ return extensors;
+ }
+
+
+ public Object getExtensionAttribute(QName name) {
+ if (delegate != null) {
+ return delegate.getExtensionAttribute(name);
+ }
+ return null == extensionAttributes ? null : extensionAttributes.get(name);
+ }
+
+ public Map<QName, Object> getExtensionAttributes() {
+ if (delegate != null) {
+ return delegate.getExtensionAttributes();
+ }
+ return extensionAttributes;
+ }
+
+ public void addExtensionAttribute(QName name, Object attr) {
+ if (delegate != null) {
+ delegate.addExtensionAttribute(name, attr);
+ return;
+ }
+ if (null == extensionAttributes) {
+ extensionAttributes = new HashMap<>();
+ }
+ extensionAttributes.put(name, attr);
+ }
+
+ public void setExtensionAttributes(Map<QName, Object> attrs) {
+ if (delegate != null) {
+ delegate.setExtensionAttributes(attrs);
+ return;
+ }
+ extensionAttributes = attrs;
+ }
+
+ /**
+ * Lookup a configuration value. This may be found in the properties holder supplied
+ * (i.e. an EndpointInfo or ServiceInfo), or it may be a property on the Bus itself.
+ * If no value is found, the defaultValue is returned.
+ *
+ * @param defaultValue the default value
+ * @param type the extensor type
+ * @return the configuration value or the default
+ */
+ public <T> T getTraversedExtensor(T defaultValue, Class<T> type) {
+ if (delegate != null) {
+ return delegate.getTraversedExtensor(defaultValue, type);
+ }
+ T extensor = getExtensor(type);
+ if (extensor == null) {
+ return defaultValue;
+ }
+ return extensor;
+ }
+
+
+ protected static final boolean equals(Object o1, Object o2) {
+ return Objects.equals(o1, o2);
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/service/model/FaultInfo.java b/transform/src/patch/java/org/apache/cxf/service/model/FaultInfo.java
new file mode 100644
index 0000000..f2f240f
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/service/model/FaultInfo.java
@@ -0,0 +1,61 @@
+/**
+ * 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.service.model;
+
+import javax.xml.namespace.QName;
+
+public class FaultInfo extends AbstractMessageContainer {
+ private QName faultName;
+
+ public FaultInfo(QName fname, QName mname, OperationInfo info) {
+ super(info, mname);
+ faultName = fname;
+ }
+
+ public QName getFaultName() {
+ return faultName;
+ }
+ public void setFaultName(QName fname) {
+ faultName = fname;
+ }
+
+
+
+ @Override
+ public int hashCode() {
+ return faultName == null ? -1 : faultName.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (!(o instanceof FaultInfo)) {
+ return false;
+ }
+ FaultInfo oi = (FaultInfo)o;
+ return equals(faultName, oi.faultName)
+ && super.equals(o);
+ }
+
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/service/model/InterfaceInfo.java b/transform/src/patch/java/org/apache/cxf/service/model/InterfaceInfo.java
new file mode 100644
index 0000000..a5c3f26
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/service/model/InterfaceInfo.java
@@ -0,0 +1,124 @@
+/**
+ * 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.service.model;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Logger;
+
+import javax.xml.namespace.QName;
+
+import org.apache.cxf.common.i18n.Message;
+import org.apache.cxf.common.logging.LogUtils;
+
+public class InterfaceInfo extends AbstractDescriptionElement implements NamedItem {
+ private static final Logger LOG = LogUtils.getL7dLogger(InterfaceInfo.class);
+
+ QName name;
+ ServiceInfo service;
+
+ Map<QName, OperationInfo> operations = new ConcurrentHashMap<>(4, 0.75f, 2);
+
+ public InterfaceInfo(ServiceInfo info, QName q) {
+ name = q;
+ service = info;
+ info.setInterface(this);
+ }
+ public DescriptionInfo getDescription() {
+ if (service == null) {
+ return null;
+ }
+ return service.getDescription();
+ }
+
+ public ServiceInfo getService() {
+ return service;
+ }
+
+ public void setName(QName n) {
+ name = n;
+ }
+ public QName getName() {
+ return name;
+ }
+
+
+ /**
+ * Adds an operation to this service.
+ *
+ * @param oname the qualified name of the operation.
+ * @return the operation.
+ */
+ public OperationInfo addOperation(QName oname) {
+ if (oname == null) {
+ throw new NullPointerException(
+ new Message("OPERATION.NAME.NOT.NULL", LOG).toString());
+ }
+ if (operations.containsKey(oname)) {
+ throw new IllegalArgumentException(
+ new Message("DUPLICATED.OPERATION.NAME", LOG, oname).toString());
+ }
+
+ OperationInfo operation = new OperationInfo(this, oname);
+ addOperation(operation);
+ return operation;
+ }
+
+ /**
+ * Adds an operation to this service.
+ *
+ * @param operation the operation.
+ */
+ void addOperation(OperationInfo operation) {
+ operations.put(operation.getName(), operation);
+ }
+
+ /**
+ * Removes an operation from this service.
+ *
+ * @param operation the operation.
+ */
+ public void removeOperation(OperationInfo operation) {
+ operations.remove(operation.getName());
+ }
+
+ /**
+ * Returns the operation info with the given name, if found.
+ *
+ * @param oname the name.
+ * @return the operation; or <code>null</code> if not found.
+ */
+ public OperationInfo getOperation(QName oname) {
+ return operations.get(oname);
+ }
+
+ /**
+ * Returns all operations for this service.
+ *
+ * @return all operations.
+ */
+ public Collection<OperationInfo> getOperations() {
+ return Collections.unmodifiableCollection(operations.values());
+ }
+
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/service/model/OperationInfo.java b/transform/src/patch/java/org/apache/cxf/service/model/OperationInfo.java
new file mode 100644
index 0000000..d5fe4d3
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/service/model/OperationInfo.java
@@ -0,0 +1,242 @@
+/**
+ * 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.service.model;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Logger;
+
+import javax.xml.namespace.QName;
+
+import org.apache.cxf.common.i18n.Message;
+import org.apache.cxf.common.logging.LogUtils;
+
+public class OperationInfo extends AbstractPropertiesHolder implements NamedItem {
+ private static final Logger LOG = LogUtils.getL7dLogger(OperationInfo.class);
+ InterfaceInfo intf;
+ QName opName;
+ String inName;
+ MessageInfo inputMessage;
+ String outName;
+ MessageInfo outputMessage;
+ Map<QName, FaultInfo> faults;
+ OperationInfo unwrappedOperation;
+ List<String> parameterOrdering;
+
+ public OperationInfo() {
+ }
+
+ OperationInfo(InterfaceInfo it, QName n) {
+ intf = it;
+ setName(n);
+ }
+ OperationInfo(OperationInfo op) {
+ intf = op.getInterface();
+ setName(op.getName());
+ }
+
+ /**
+ * Returns the name of the Operation.
+ * @return the name of the Operation
+ */
+ public QName getName() {
+ return opName;
+ }
+ /**
+ * Sets the name of the operation.
+ * @param name the new name of the operation
+ */
+ public final void setName(QName name) {
+ if (name == null) {
+ throw new NullPointerException("Operation Name cannot be null.");
+ }
+ opName = name;
+ }
+ public InterfaceInfo getInterface() {
+ return intf;
+ }
+
+
+ public MessageInfo createMessage(QName nm, MessageInfo.Type type) {
+ return new MessageInfo(this, type, nm);
+ }
+
+ public MessageInfo getOutput() {
+ return outputMessage;
+ }
+ public String getOutputName() {
+ return outName;
+ }
+ public void setOutput(String nm, MessageInfo out) {
+ outName = nm;
+ outputMessage = out;
+ if (unwrappedOperation != null && unwrappedOperation.getOutput() != null) {
+ unwrappedOperation.getOutput().setDelegate(out, false);
+ }
+ }
+ public boolean hasOutput() {
+ return outputMessage != null;
+ }
+
+ public MessageInfo getInput() {
+ return inputMessage;
+ }
+ public String getInputName() {
+ return inName;
+ }
+ public void setInput(String nm, MessageInfo in) {
+ inName = nm;
+ inputMessage = in;
+ if (unwrappedOperation != null && unwrappedOperation.getInput() != null) {
+ unwrappedOperation.getInput().setDelegate(in, false);
+ }
+ }
+ public boolean hasInput() {
+ return inputMessage != null;
+ }
+
+ public boolean isOneWay() {
+ return inputMessage != null && outputMessage == null;
+ }
+
+ public boolean isUnwrappedCapable() {
+ return unwrappedOperation != null;
+ }
+
+ public OperationInfo getUnwrappedOperation() {
+ return unwrappedOperation;
+ }
+ public void setUnwrappedOperation(OperationInfo op) {
+ unwrappedOperation = op;
+ }
+ public boolean isUnwrapped() {
+ return false;
+ }
+
+
+ /**
+ * Adds an fault to this operation.
+ *
+ * @param name the fault name.
+ */
+ public FaultInfo addFault(QName name, QName message) {
+ if (name == null) {
+ throw new NullPointerException(new Message("FAULT.NAME.NOT.NULL", LOG).toString());
+ }
+ if (faults != null && faults.containsKey(name)) {
+ throw new IllegalArgumentException(
+ new Message("DUPLICATED.FAULT.NAME", LOG, name).toString());
+ }
+ FaultInfo fault = new FaultInfo(name, message, this);
+ addFault(fault);
+ return fault;
+ }
+
+ /**
+ * Adds a fault to this operation.
+ *
+ * @param fault the fault.
+ */
+ public synchronized void addFault(FaultInfo fault) {
+ if (faults == null) {
+ faults = new ConcurrentHashMap<>(4, 0.75f, 2);
+ }
+ faults.put(fault.getFaultName(), fault);
+ }
+
+ /**
+ * Removes a fault from this operation.
+ *
+ * @param name the qualified fault name.
+ */
+ public void removeFault(QName name) {
+ if (faults != null) {
+ faults.remove(name);
+ }
+ }
+
+ /**
+ * Returns the fault with the given name, if found.
+ *
+ * @param name the name.
+ * @return the fault; or <code>null</code> if not found.
+ */
+ public FaultInfo getFault(QName name) {
+ if (faults != null) {
+ return faults.get(name);
+ }
+ return null;
+ }
+
+ public boolean hasFaults() {
+ return faults != null && !faults.isEmpty();
+ }
+
+ /**
+ * Returns all faults for this operation.
+ *
+ * @return all faults.
+ */
+ public Collection<FaultInfo> getFaults() {
+ if (faults == null) {
+ return Collections.emptyList();
+ }
+ return Collections.unmodifiableCollection(faults.values());
+ }
+
+ public void setParameterOrdering(List<String> o) {
+ this.parameterOrdering = o;
+ }
+
+ public List<String> getParameterOrdering() {
+ return parameterOrdering;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder("[OperationInfo: ")
+ .append(opName)
+ .append(']').toString();
+ }
+
+ public int hashCode() {
+ return opName == null ? -1 : opName.hashCode();
+ }
+
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (!(o instanceof OperationInfo)) {
+ return false;
+ }
+ OperationInfo oi = (OperationInfo)o;
+ return equals(opName, oi.opName)
+ && equals(inputMessage, oi.inputMessage)
+ && equals(outputMessage, oi.outputMessage)
+ && equals(faults, oi.faults)
+ && equals(intf.getName(), oi.intf.getName());
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/service/model/UnwrappedOperationInfo.java b/transform/src/patch/java/org/apache/cxf/service/model/UnwrappedOperationInfo.java
new file mode 100644
index 0000000..31ed72c
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/service/model/UnwrappedOperationInfo.java
@@ -0,0 +1,72 @@
+/**
+ * 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.service.model;
+
+import java.util.Collection;
+
+import javax.xml.namespace.QName;
+
+public class UnwrappedOperationInfo extends OperationInfo {
+ OperationInfo wrappedOp;
+
+ public UnwrappedOperationInfo(OperationInfo op) {
+ super(op);
+ wrappedOp = op;
+ setDelegate(wrappedOp, true);
+ }
+
+ public OperationInfo getWrappedOperation() {
+ return wrappedOp;
+ }
+
+ @Override
+ public boolean isUnwrapped() {
+ return true;
+ }
+
+ @Override
+ public FaultInfo addFault(QName name, QName message) {
+ return wrappedOp.addFault(name, message);
+ }
+
+ @Override
+ public FaultInfo getFault(QName name) {
+ return wrappedOp.getFault(name);
+ }
+
+ @Override
+ public Collection<FaultInfo> getFaults() {
+ return wrappedOp.getFaults();
+ }
+
+
+ @Override
+ public void setOutput(String nm, MessageInfo out) {
+ super.setOutput(nm, out);
+ out.setDelegate(wrappedOp.getOutput(), false);
+ }
+
+ @Override
+ public void setInput(String nm, MessageInfo in) {
+ super.setInput(nm, in);
+ in.setDelegate(wrappedOp.getInput(), false);
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/staxutils/StaxUtils.java b/transform/src/patch/java/org/apache/cxf/staxutils/StaxUtils.java
new file mode 100644
index 0000000..440968c
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/staxutils/StaxUtils.java
@@ -0,0 +1,2222 @@
+/**
+ * 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.staxutils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.Location;
+import javax.xml.stream.StreamFilter;
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLResolver;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.DTD;
+import javax.xml.stream.events.Namespace;
+import javax.xml.stream.events.StartDocument;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.sax.SAXSource;
+import javax.xml.transform.stax.StAXSource;
+import javax.xml.transform.stream.StreamSource;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.CDATASection;
+import org.w3c.dom.Comment;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentFragment;
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.Element;
+import org.w3c.dom.EntityReference;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.ProcessingInstruction;
+import org.w3c.dom.Text;
+import org.w3c.dom.UserDataHandler;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.PropertyUtils;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.common.util.SystemPropertyAction;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.helpers.DOMUtils;
+import org.apache.cxf.message.Message;
+
+public final class StaxUtils {
+ // System properties for defaults, but also contextual properties usable
+ // for StaxInInterceptor
+ public static final String MAX_CHILD_ELEMENTS =
+ "org.apache.cxf.stax.maxChildElements";
+ public static final String MAX_ELEMENT_DEPTH =
+ "org.apache.cxf.stax.maxElementDepth";
+ public static final String MAX_ATTRIBUTE_COUNT =
+ "org.apache.cxf.stax.maxAttributeCount";
+ public static final String MAX_ATTRIBUTE_SIZE =
+ "org.apache.cxf.stax.maxAttributeSize";
+ public static final String MAX_TEXT_LENGTH =
+ "org.apache.cxf.stax.maxTextLength";
+ public static final String MIN_TEXT_SEGMENT =
+ "org.apache.cxf.stax.minTextSegment";
+ public static final String MAX_ELEMENT_COUNT =
+ "org.apache.cxf.stax.maxElementCount";
+ public static final String MAX_XML_CHARACTERS =
+ "org.apache.cxf.stax.maxXMLCharacters";
+
+ public static final String ALLOW_INSECURE_PARSER =
+ "org.apache.cxf.stax.allowInsecureParser";
+
+ private static final String INNER_ELEMENT_COUNT_SYSTEM_PROP =
+ "org.apache.cxf.staxutils.innerElementCountThreshold";
+ private static final String INNER_ELEMENT_LEVEL_SYSTEM_PROP =
+ "org.apache.cxf.staxutils.innerElementLevelThreshold";
+ private static final String AUTO_CLOSE_INPUT_SOURCE_PROP =
+ "org.apache.cxf.staxutils.autoCloseInputSource";
+
+ private static final Logger LOG = LogUtils.getL7dLogger(StaxUtils.class);
+
+ private static final Queue<XMLInputFactory> NS_AWARE_INPUT_FACTORY_POOL;
+ private static final XMLInputFactory SAFE_INPUT_FACTORY;
+ private static final Queue<XMLOutputFactory> OUTPUT_FACTORY_POOL;
+ private static final XMLOutputFactory SAFE_OUTPUT_FACTORY;
+
+ private static final String XML_NS = "http://www.w3.org/2000/xmlns/";
+ private static final String[] DEF_PREFIXES = new String[] {
+ "ns1".intern(), "ns2".intern(), "ns3".intern(),
+ "ns4".intern(), "ns5".intern(), "ns6".intern(),
+ "ns7".intern(), "ns8".intern(), "ns9".intern()
+ };
+
+ private static final int MAX_ATTR_COUNT_VAL =
+ getInteger(MAX_ATTRIBUTE_COUNT, 500);
+ private static final int MAX_ATTR_SIZE_VAL =
+ getInteger(MAX_ATTRIBUTE_SIZE, 64 * 1024); //64K per attribute, likely just "list" will hit
+ private static final int MAX_TEXT_LENGTH_VAL =
+ getInteger(MAX_TEXT_LENGTH, 128 * 1024 * 1024); //128M - more than this should DEFINITELY use MTOM
+ private static final int MIN_TEXT_SEGMENT_VAL =
+ getInteger(MIN_TEXT_SEGMENT, 64); // Same default as woodstox
+ private static final long MAX_ELEMENT_COUNT_VAL =
+ getLong(MAX_ELEMENT_COUNT, Long.MAX_VALUE);
+ private static final long MAX_XML_CHARS_VAL =
+ getLong(MAX_XML_CHARACTERS, Long.MAX_VALUE);
+ private static final int PARSER_POOL_SIZE_VAL =
+ getInteger("org.apache.cxf.staxutils.pool-size", 20);
+ private static final boolean ALLOW_INSECURE_PARSER_VAL;
+ private static final boolean AUTO_CLOSE_INPUT_SOURCE;
+
+ // Here we check old names first and then new names for the threshold properties
+ private static final int MAX_ELEMENT_DEPTH_VAL =
+ getInteger(MAX_ELEMENT_DEPTH, getInteger(INNER_ELEMENT_LEVEL_SYSTEM_PROP, 100));
+ private static final int MAX_CHILD_ELEMENTS_VAL =
+ getInteger(MAX_CHILD_ELEMENTS, getInteger(INNER_ELEMENT_COUNT_SYSTEM_PROP, 50000));
+
+ // Variables from Woodstox
+ private static final String P_MAX_ATTRIBUTES_PER_ELEMENT = "com.ctc.wstx.maxAttributesPerElement";
+ private static final String P_MAX_ATTRIBUTE_SIZE = "com.ctc.wstx.maxAttributeSize";
+ private static final String P_MAX_TEXT_LENGTH = "com.ctc.wstx.maxTextLength";
+ private static final String P_MAX_ELEMENT_COUNT = "com.ctc.wstx.maxElementCount";
+ private static final String P_MAX_CHARACTERS = "com.ctc.wstx.maxCharacters";
+ private static final String P_MAX_ELEMENT_DEPTH = "com.ctc.wstx.maxElementDepth";
+ private static final String P_MAX_CHILDREN_PER_ELEMENT = "com.ctc.wstx.maxChildrenPerElement";
+ private static final String P_MIN_TEXT_SEGMENT = "com.ctc.wstx.minTextSegment";
+
+
+ static {
+ NS_AWARE_INPUT_FACTORY_POOL = new ArrayBlockingQueue<>(PARSER_POOL_SIZE_VAL);
+ OUTPUT_FACTORY_POOL = new ArrayBlockingQueue<>(PARSER_POOL_SIZE_VAL);
+
+ String allowInsecureParser = SystemPropertyAction.getPropertyOrNull(ALLOW_INSECURE_PARSER);
+ if (!StringUtils.isEmpty(allowInsecureParser)) {
+ ALLOW_INSECURE_PARSER_VAL = "1".equals(allowInsecureParser) || Boolean.parseBoolean(allowInsecureParser);
+ } else {
+ ALLOW_INSECURE_PARSER_VAL = false;
+ }
+
+ String autoCloseInputSource = SystemPropertyAction.getPropertyOrNull(AUTO_CLOSE_INPUT_SOURCE_PROP);
+ if (!StringUtils.isEmpty(autoCloseInputSource)) {
+ AUTO_CLOSE_INPUT_SOURCE = "1".equals(autoCloseInputSource) || Boolean.parseBoolean(autoCloseInputSource);
+ } else {
+ AUTO_CLOSE_INPUT_SOURCE = false; /* set 'false' by default */
+ }
+
+ XMLInputFactory xif = null;
+ try {
+ xif = createXMLInputFactory(true);
+ String xifClassName = xif.getClass().getName();
+ if (!xifClassName.contains("ctc.wstx") && !xifClassName.contains("xml.xlxp")
+ && !xifClassName.contains("xml.xlxp2") && !xifClassName.contains("bea.core")) {
+ xif = null;
+ }
+ } catch (Throwable t) {
+ //ignore, can always drop down to the pooled factories
+ }
+ SAFE_INPUT_FACTORY = xif;
+
+ XMLOutputFactory xof = null;
+ try {
+ xof = XMLOutputFactory.newInstance();
+ String xofClassName = xof.getClass().getName();
+ if (!xofClassName.contains("ctc.wstx") && !xofClassName.contains("xml.xlxp")
+ && !xofClassName.contains("xml.xlxp2") && !xofClassName.contains("bea.core")) {
+ xof = null;
+ }
+ } catch (Throwable t) {
+ //ignore, can always drop down to the pooled factories
+ }
+ SAFE_OUTPUT_FACTORY = xof;
+
+ }
+
+ private StaxUtils() {
+ }
+ private static int getInteger(String prop, int def) {
+ try {
+ String s = SystemPropertyAction.getPropertyOrNull(prop);
+ if (StringUtils.isEmpty(s)) {
+ return def;
+ }
+ int i = Integer.parseInt(s);
+ if (i < 0) {
+ i = def;
+ }
+ return i;
+ } catch (Throwable t) {
+ //ignore
+ }
+ return def;
+ }
+ private static long getLong(String prop, long def) {
+ try {
+ String s = SystemPropertyAction.getPropertyOrNull(prop);
+ if (StringUtils.isEmpty(s)) {
+ return def;
+ }
+ long i = Long.parseLong(s);
+ if (i < 0) {
+ i = def;
+ }
+ return i;
+ } catch (Throwable t) {
+ //ignore
+ }
+ return def;
+ }
+
+ /**
+ * Return a cached, namespace-aware, factory.
+ */
+ private static XMLInputFactory getXMLInputFactory() {
+ if (SAFE_INPUT_FACTORY != null) {
+ return SAFE_INPUT_FACTORY;
+ }
+ XMLInputFactory f = NS_AWARE_INPUT_FACTORY_POOL.poll();
+ if (f == null) {
+ f = createXMLInputFactory(true);
+ }
+ return f;
+ }
+
+ private static void returnXMLInputFactory(XMLInputFactory factory) {
+ if (SAFE_INPUT_FACTORY != factory) {
+ NS_AWARE_INPUT_FACTORY_POOL.offer(factory);
+ }
+ }
+
+ private static XMLOutputFactory getXMLOutputFactory() {
+ if (SAFE_OUTPUT_FACTORY != null) {
+ return SAFE_OUTPUT_FACTORY;
+ }
+ XMLOutputFactory f = OUTPUT_FACTORY_POOL.poll();
+ if (f == null) {
+ f = XMLOutputFactory.newInstance();
+ }
+ return f;
+ }
+
+ private static void returnXMLOutputFactory(XMLOutputFactory factory) {
+ if (SAFE_OUTPUT_FACTORY != factory) {
+ OUTPUT_FACTORY_POOL.offer(factory);
+ }
+ }
+
+ /**
+ * Return a new factory so that the caller can set sticky parameters.
+ * @param nsAware
+ * @throws XMLStreamException
+ */
+ public static XMLInputFactory createXMLInputFactory(boolean nsAware) {
+ XMLInputFactory factory = null;
+ try {
+ factory = XMLInputFactory.newInstance();
+ } catch (Throwable t) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.log(Level.FINE, "XMLInputFactory.newInstance() failed with: ", t);
+ }
+ }
+ if (factory == null || !setRestrictionProperties(factory)) {
+ try {
+ factory = createWoodstoxFactory();
+ } catch (Throwable t) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.log(Level.FINE, "Cannot create Woodstox XMLInputFactory: ", t);
+ }
+ }
+
+ if (factory == null) {
+ throw new RuntimeException("Failed to create XMLInputFactory.");
+ }
+
+ if (!setRestrictionProperties(factory)) {
+ if (ALLOW_INSECURE_PARSER_VAL) {
+ LOG.log(Level.WARNING, "INSECURE_PARSER_DETECTED", factory.getClass().getName());
+ } else {
+ throw new RuntimeException("Cannot create a secure XMLInputFactory, "
+ + "you should either add woodstox or set " + ALLOW_INSECURE_PARSER
+ + " system property to true if an unsafe mode is acceptable.");
+ }
+ }
+ }
+ setProperty(factory, XMLInputFactory.IS_NAMESPACE_AWARE, nsAware);
+ setProperty(factory, XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
+ setProperty(factory, XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.FALSE);
+ setProperty(factory, XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
+ factory.setXMLResolver(new XMLResolver() {
+ public Object resolveEntity(String publicID, String systemID,
+ String baseURI, String namespace)
+ throws XMLStreamException {
+ throw new XMLStreamException("Reading external entities is disabled");
+ }
+ });
+
+ return factory;
+ }
+
+ private static XMLInputFactory createWoodstoxFactory() {
+ return WoodstoxHelper.createInputFactory();
+ }
+
+ public static XMLEventFactory createWoodstoxEventFactory() {
+ return WoodstoxHelper.createEventFactory();
+ }
+
+ private static boolean setRestrictionProperties(XMLInputFactory factory) {
+ //For now, we can only support Woodstox 4.2.x and newer as none of the other
+ //stax parsers support these settings
+ final boolean wstxMaxs = setProperty(factory, P_MAX_ATTRIBUTES_PER_ELEMENT, MAX_ATTR_COUNT_VAL)
+ && setProperty(factory, P_MAX_ATTRIBUTE_SIZE, MAX_ATTR_SIZE_VAL)
+ && setProperty(factory, P_MAX_CHILDREN_PER_ELEMENT, MAX_CHILD_ELEMENTS_VAL)
+ && setProperty(factory, P_MAX_ELEMENT_COUNT, MAX_ELEMENT_COUNT_VAL)
+ && setProperty(factory, P_MAX_ELEMENT_DEPTH, MAX_ELEMENT_DEPTH_VAL)
+ && setProperty(factory, P_MAX_CHARACTERS, MAX_XML_CHARS_VAL)
+ && setProperty(factory, P_MAX_TEXT_LENGTH, MAX_TEXT_LENGTH_VAL);
+ return wstxMaxs
+ && setProperty(factory, P_MIN_TEXT_SEGMENT, MIN_TEXT_SEGMENT_VAL);
+ }
+
+ private static boolean setProperty(XMLInputFactory f, String p, Object o) {
+ try {
+ f.setProperty(p, o);
+ return true;
+ } catch (Throwable t) {
+ //ignore
+ }
+ return false;
+ }
+
+
+
+ public static XMLStreamWriter createXMLStreamWriter(Writer out) {
+ XMLOutputFactory factory = getXMLOutputFactory();
+ try {
+ return factory.createXMLStreamWriter(out);
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Cant' create XMLStreamWriter", e);
+ } finally {
+ returnXMLOutputFactory(factory);
+ }
+ }
+
+ public static XMLStreamWriter createXMLStreamWriter(OutputStream out) {
+ return createXMLStreamWriter(out, null);
+ }
+
+ public static XMLStreamWriter createXMLStreamWriter(OutputStream out, String encoding) {
+ XMLOutputFactory factory = getXMLOutputFactory();
+ try {
+ return factory.createXMLStreamWriter(out, encoding != null ? encoding : StandardCharsets.UTF_8.name());
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Cant' create XMLStreamWriter", e);
+ } finally {
+ returnXMLOutputFactory(factory);
+ }
+ }
+
+ public static XMLStreamWriter createXMLStreamWriter(Result r) {
+ if (r instanceof DOMResult) {
+ //use our own DOM writer to avoid issues with Sun's
+ //version that doesn't support getNamespaceContext
+ DOMResult dr = (DOMResult)r;
+ Node nd = dr.getNode();
+ if (nd instanceof Document) {
+ return new W3CDOMStreamWriter((Document)nd);
+ } else if (nd instanceof Element) {
+ return new W3CDOMStreamWriter((Element)nd);
+ } else if (nd instanceof DocumentFragment) {
+ return new W3CDOMStreamWriter((DocumentFragment)nd);
+ }
+ }
+ XMLOutputFactory factory = getXMLOutputFactory();
+ try {
+ return factory.createXMLStreamWriter(r);
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Cant' create XMLStreamWriter", e);
+ } finally {
+ returnXMLOutputFactory(factory);
+ }
+ }
+
+ public static XMLStreamReader createFilteredReader(XMLStreamReader reader, StreamFilter filter) {
+ XMLInputFactory factory = getXMLInputFactory();
+ try {
+ return factory.createFilteredReader(reader, filter);
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Cant' create XMLStreamReader", e);
+ } finally {
+ returnXMLInputFactory(factory);
+ }
+ }
+
+
+ public static void nextEvent(XMLStreamReader dr) {
+ try {
+ dr.next();
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Couldn't parse stream.", e);
+ }
+ }
+
+ public static boolean toNextText(DepthXMLStreamReader reader) {
+ if (reader.getEventType() == XMLStreamConstants.CHARACTERS) {
+ return true;
+ }
+
+ try {
+ int depth = reader.getDepth();
+ int event = reader.getEventType();
+ while (reader.getDepth() >= depth && reader.hasNext()) {
+ if (event == XMLStreamConstants.CHARACTERS && reader.getDepth() == depth + 1) {
+ return true;
+ }
+ event = reader.next();
+ }
+ return false;
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Couldn't parse stream.", e);
+ }
+ }
+ public static boolean toNextTag(XMLStreamReader reader) {
+ try {
+ // advance to first tag.
+ int x = reader.getEventType();
+ while (x != XMLStreamConstants.START_ELEMENT
+ && x != XMLStreamConstants.END_ELEMENT
+ && reader.hasNext()) {
+ x = reader.next();
+ }
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Couldn't parse stream.", e);
+ }
+ return true;
+ }
+
+ public static boolean toNextTag(DepthXMLStreamReader reader, QName endTag) {
+ try {
+ int depth = reader.getDepth();
+ int event = reader.getEventType();
+ while (reader.getDepth() >= depth && reader.hasNext()) {
+ if (event == XMLStreamConstants.START_ELEMENT && reader.getName().equals(endTag)
+ && reader.getDepth() == depth + 1) {
+ return true;
+ }
+ event = reader.next();
+ }
+ return false;
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Couldn't parse stream.", e);
+ }
+ }
+
+ public static void writeStartElement(XMLStreamWriter writer, String prefix, String name, String namespace)
+ throws XMLStreamException {
+ if (prefix == null) {
+ prefix = "";
+ }
+
+ if (!namespace.isEmpty()) {
+ writer.writeStartElement(prefix, name, namespace);
+ if (!prefix.isEmpty()) {
+ writer.writeNamespace(prefix, namespace);
+ writer.setPrefix(prefix, namespace);
+ } else {
+ writer.writeDefaultNamespace(namespace);
+ writer.setDefaultNamespace(namespace);
+ }
+ } else {
+ writer.writeStartElement(name);
+ writer.writeDefaultNamespace("");
+ writer.setDefaultNamespace("");
+ }
+ }
+
+ /**
+ * Returns true if currently at the start of an element, otherwise move
+ * forwards to the next element start and return true, otherwise false is
+ * returned if the end of the stream is reached.
+ */
+ public static boolean skipToStartOfElement(XMLStreamReader in) throws XMLStreamException {
+ for (int code = in.getEventType(); code != XMLStreamConstants.END_DOCUMENT; code = in.next()) {
+ if (code == XMLStreamConstants.START_ELEMENT) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean toNextElement(DepthXMLStreamReader dr) {
+ if (dr.getEventType() == XMLStreamConstants.START_ELEMENT) {
+ return true;
+ }
+ if (dr.getEventType() == XMLStreamConstants.END_ELEMENT) {
+ return false;
+ }
+ try {
+ int depth = dr.getDepth();
+
+ for (int event = dr.getEventType(); dr.getDepth() >= depth && dr.hasNext(); event = dr.next()) {
+ if (event == XMLStreamConstants.START_ELEMENT && dr.getDepth() == depth + 1) {
+ return true;
+ } else if (event == XMLStreamConstants.END_ELEMENT) {
+ depth--;
+ }
+ }
+
+ return false;
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Couldn't parse stream.", e);
+ }
+ }
+
+ public static boolean skipToStartOfElement(DepthXMLStreamReader in) throws XMLStreamException {
+ for (int code = in.getEventType(); code != XMLStreamConstants.END_DOCUMENT; code = in.next()) {
+ if (code == XMLStreamConstants.START_ELEMENT) {
+ return true;
+ }
+ }
+ return false;
+ }
+ public static void copy(Source source, OutputStream os) throws XMLStreamException {
+ XMLStreamWriter writer = createXMLStreamWriter(os);
+ try {
+ copy(source, writer);
+ } finally {
+ try {
+ writer.flush();
+ } catch (XMLStreamException ex) {
+ //ignore
+ }
+ StaxUtils.close(writer);
+ }
+ }
+ public static void copy(Source source, XMLStreamWriter writer) throws XMLStreamException {
+ if (source instanceof StaxSource) {
+ StaxSource ss = (StaxSource)source;
+ if (ss.getXMLStreamReader() == null) {
+ return;
+ }
+ } else if (source instanceof StAXSource) {
+ StAXSource ss = (StAXSource)source;
+ if (ss.getXMLStreamReader() == null) {
+ return;
+ }
+ } else if (source instanceof SAXSource) {
+ SAXSource ss = (SAXSource)source;
+ InputSource src = ss.getInputSource();
+ if (src == null || (src.getSystemId() == null && src.getPublicId() == null)) {
+ if (ss.getXMLReader() != null) {
+ //OK - reader is OK. We'll use that out
+ StreamWriterContentHandler ch = new StreamWriterContentHandler(writer);
+ XMLReader reader = ((SAXSource)source).getXMLReader();
+ reader.setContentHandler(ch);
+ try {
+ try {
+ reader.setFeature("http://xml.org/sax/features/namespaces", true);
+ } catch (Throwable t) {
+ //ignore
+ }
+ try {
+ reader.setProperty("http://xml.org/sax/properties/lexical-handler", ch);
+ } catch (Throwable t) {
+ //ignore
+ }
+ reader.parse(((SAXSource)source).getInputSource());
+ return;
+ } catch (Exception e) {
+ throw new XMLStreamException(e.getMessage(), e);
+ }
+ } else if (ss.getInputSource() == null) {
+ //nothing to copy, just return
+ return;
+ }
+ }
+
+ } else if (source instanceof StreamSource) {
+ StreamSource ss = (StreamSource)source;
+ if (ss.getInputStream() == null
+ && ss.getReader() == null
+ && ss.getSystemId() == null) {
+ //nothing to copy, just return
+ return;
+ }
+ }
+ XMLStreamReader reader = createXMLStreamReader(source);
+ copy(reader, writer);
+ reader.close();
+ }
+
+ public static Document copy(Document doc)
+ throws XMLStreamException, ParserConfigurationException {
+
+ XMLStreamReader reader = createXMLStreamReader(doc);
+ W3CDOMStreamWriter writer = new W3CDOMStreamWriter();
+ copy(reader, writer);
+ Document d = writer.getDocument();
+ try {
+ d.setDocumentURI(doc.getDocumentURI());
+ } catch (Exception ex) {
+ //ignore - probably not DOM level 3
+ }
+ return d;
+ }
+ public static void copy(Document doc, XMLStreamWriter writer) throws XMLStreamException {
+ XMLStreamReader reader = createXMLStreamReader(doc);
+ copy(reader, writer);
+ }
+ public static void copy(Element node, XMLStreamWriter writer) throws XMLStreamException {
+ XMLStreamReader reader = createXMLStreamReader(node);
+ copy(reader, writer);
+ }
+
+ public static void copy(XMLStreamReader reader, OutputStream os)
+ throws XMLStreamException {
+ XMLStreamWriter xsw = StaxUtils.createXMLStreamWriter(os);
+ StaxUtils.copy(reader, xsw);
+ xsw.close();
+ }
+
+ public static void writeTo(Node node, OutputStream os) throws XMLStreamException {
+ copy(new DOMSource(node), os);
+ }
+ public static void writeTo(Node node, OutputStream os, int indent) throws XMLStreamException {
+ if (indent > 0) {
+ XMLStreamWriter writer = new PrettyPrintXMLStreamWriter(createXMLStreamWriter(os), indent);
+ try {
+ copy(new DOMSource(node), writer);
+ } finally {
+ writer.close();
+ }
+ } else {
+ copy(new DOMSource(node), os);
+ }
+ }
+ public static void writeTo(Node node, Writer os) throws XMLStreamException {
+ writeTo(node, os, 0);
+ }
+ public static void writeTo(Node node, Writer os, int indent) throws XMLStreamException {
+ XMLStreamWriter writer = createXMLStreamWriter(os);
+ if (indent > 0) {
+ writer = new PrettyPrintXMLStreamWriter(writer, indent);
+ }
+ try {
+ copy(new DOMSource(node), writer);
+ } finally {
+ writer.close();
+ }
+ }
+
+
+ /**
+ * Copies the reader to the writer. The start and end document methods must
+ * be handled on the writer manually.
+ *
+ * @param reader
+ * @param writer
+ * @throws XMLStreamException
+ */
+ public static void copy(XMLStreamReader reader, XMLStreamWriter writer) throws XMLStreamException {
+ copy(reader, writer, false, false);
+ }
+ public static void copy(XMLStreamReader reader, XMLStreamWriter writer, boolean fragment)
+ throws XMLStreamException {
+ copy(reader, writer, fragment, false);
+ }
+ public static void copy(XMLStreamReader reader,
+ XMLStreamWriter writer,
+ boolean fragment,
+ boolean isThreshold) throws XMLStreamException {
+ // number of elements read in
+ int read = 0;
+ int elementCount = 0;
+ final Deque<Integer> countStack = new ArrayDeque<>();
+ int event = reader.getEventType();
+
+ while (reader.hasNext()) {
+ switch (event) {
+ case XMLStreamConstants.START_ELEMENT:
+ read++;
+ if (isThreshold) {
+ elementCount++;
+
+ if (MAX_ELEMENT_DEPTH_VAL != -1 && read >= MAX_ELEMENT_DEPTH_VAL) {
+ throw new DepthExceededStaxException("reach the innerElementLevelThreshold:"
+ + MAX_ELEMENT_DEPTH_VAL);
+ }
+ if (MAX_CHILD_ELEMENTS_VAL != -1 && elementCount >= MAX_CHILD_ELEMENTS_VAL) {
+ throw new DepthExceededStaxException("reach the innerElementCountThreshold:"
+ + MAX_CHILD_ELEMENTS_VAL);
+ }
+ countStack.push(elementCount);
+ elementCount = 0;
+ }
+ writeStartElement(reader, writer);
+ break;
+ case XMLStreamConstants.END_ELEMENT:
+ if (read > 0) {
+ writer.writeEndElement();
+ }
+ read--;
+ if (read < 0 && fragment) {
+ return;
+ }
+ if (isThreshold && !countStack.isEmpty()) {
+ elementCount = countStack.pop();
+ }
+ break;
+ case XMLStreamConstants.CHARACTERS:
+ case XMLStreamConstants.SPACE:
+ String s = reader.getText();
+ if (s != null) {
+ writer.writeCharacters(s);
+ }
+ break;
+ case XMLStreamConstants.COMMENT:
+ writer.writeComment(reader.getText());
+ break;
+ case XMLStreamConstants.CDATA:
+ writer.writeCData(reader.getText());
+ break;
+ case XMLStreamConstants.START_DOCUMENT:
+ case XMLStreamConstants.END_DOCUMENT:
+ case XMLStreamConstants.ATTRIBUTE:
+ case XMLStreamConstants.NAMESPACE:
+ break;
+ default:
+ break;
+ }
+ event = reader.next();
+ }
+ }
+
+ private static void writeStartElement(XMLStreamReader reader, XMLStreamWriter writer)
+ throws XMLStreamException {
+ String uri = reader.getNamespaceURI();
+ String prefix = reader.getPrefix();
+ String local = reader.getLocalName();
+
+ if (prefix == null) {
+ prefix = "";
+ }
+
+ boolean writeElementNS = false;
+
+ if (uri != null) {
+ writeElementNS = true;
+ Iterator<String> it = CastUtils.cast(writer.getNamespaceContext().getPrefixes(uri));
+ if (!it.hasNext() && StringUtils.isEmpty(prefix) && StringUtils.isEmpty(uri)
+ && StringUtils.isEmpty(writer.getNamespaceContext().getNamespaceURI(""))) {
+ writeElementNS = false;
+ }
+ while (it.hasNext()) {
+ String s = it.next();
+ if (s == null) {
+ s = "";
+ }
+ if (s.equals(prefix)) {
+ writeElementNS = false;
+ }
+ }
+ }
+
+ // Write out the element name
+ if (uri != null) {
+ if (prefix.isEmpty() && StringUtils.isEmpty(uri)) {
+ writer.writeStartElement(local);
+ } else {
+ writer.writeStartElement(prefix, local, uri);
+ }
+ } else {
+ writer.writeStartElement(local);
+ }
+
+ // Write out the namespaces
+ for (int i = 0; i < reader.getNamespaceCount(); i++) {
+ String nsURI = reader.getNamespaceURI(i);
+ String nsPrefix = reader.getNamespacePrefix(i);
+ if (nsPrefix == null) {
+ nsPrefix = "";
+ }
+ if (nsURI == null) {
+ nsURI = "";
+ }
+ if (nsPrefix.isEmpty()) {
+ writer.writeDefaultNamespace(nsURI);
+ writer.setDefaultNamespace(nsURI);
+ } else {
+ writer.writeNamespace(nsPrefix, nsURI);
+ writer.setPrefix(nsPrefix, nsURI);
+ }
+
+ if (nsURI.equals(uri) && nsPrefix.equals(prefix)) {
+ writeElementNS = false;
+ }
+ }
+
+ // Check if the namespace still needs to be written.
+ // We need this check because namespace writing works
+ // different on Woodstox and the RI.
+ if (writeElementNS) {
+ if (prefix.isEmpty()) {
+ writer.writeDefaultNamespace(uri);
+ writer.setDefaultNamespace(uri);
+ } else {
+ writer.writeNamespace(prefix, uri);
+ writer.setPrefix(prefix, uri);
+ }
+ }
+
+ // Write out attributes
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String ns = reader.getAttributeNamespace(i);
+ String nsPrefix = reader.getAttributePrefix(i);
+ if (ns == null || ns.isEmpty()) {
+ writer.writeAttribute(reader.getAttributeLocalName(i), reader.getAttributeValue(i));
+ } else if (nsPrefix == null || nsPrefix.isEmpty()) {
+ writer.writeAttribute(reader.getAttributeNamespace(i), reader.getAttributeLocalName(i),
+ reader.getAttributeValue(i));
+ } else {
+ Iterator<String> it = CastUtils.cast(writer.getNamespaceContext().getPrefixes(ns));
+ boolean writeNs = true;
+ while (it != null && it.hasNext()) {
+ String s = it.next();
+ if (s == null) {
+ s = "";
+ }
+ if (s.equals(nsPrefix)) {
+ writeNs = false;
+ }
+ }
+ if (writeNs) {
+ writer.writeNamespace(nsPrefix, ns);
+ writer.setPrefix(nsPrefix, ns);
+ }
+ writer.writeAttribute(reader.getAttributePrefix(i), reader.getAttributeNamespace(i), reader
+ .getAttributeLocalName(i), reader.getAttributeValue(i));
+ }
+
+ }
+ }
+
+ public static void writeDocument(Document d, XMLStreamWriter writer, boolean repairing)
+ throws XMLStreamException {
+ writeDocument(d, writer, true, repairing);
+ }
+
+ public static void writeDocument(Document d, XMLStreamWriter writer, boolean writeProlog,
+ boolean repairing) throws XMLStreamException {
+ if (writeProlog) {
+ writer.writeStartDocument();
+ }
+
+ Node node = d.getFirstChild();
+ while (node != null) {
+ if (writeProlog || node.getNodeType() == Node.ELEMENT_NODE) {
+ writeNode(node, writer, repairing);
+ }
+ node = node.getNextSibling();
+ }
+
+ if (writeProlog) {
+ writer.writeEndDocument();
+ }
+ }
+
+ /**
+ * Writes an Element to an XMLStreamWriter. The writer must already have
+ * started the document (via writeStartDocument()). Also, this probably
+ * won't work with just a fragment of a document. The Element should be the
+ * root element of the document.
+ *
+ * @param e
+ * @param writer
+ * @throws XMLStreamException
+ */
+ public static void writeElement(Element e, XMLStreamWriter writer, boolean repairing)
+ throws XMLStreamException {
+ writeElement(e, writer, repairing, true);
+ }
+
+ /**
+ * Writes an Element to an XMLStreamWriter. The writer must already have
+ * started the document (via writeStartDocument()). Also, this probably
+ * won't work with just a fragment of a document. The Element should be the
+ * root element of the document.
+ *
+ * @param e
+ * @param writer
+ * @param endElement true if the element should be ended
+ * @throws XMLStreamException
+ */
+ public static void writeElement(Element e,
+ XMLStreamWriter writer,
+ boolean repairing,
+ boolean endElement)
+ throws XMLStreamException {
+ String prefix = e.getPrefix();
+ String ns = e.getNamespaceURI();
+ String localName = e.getLocalName();
+
+ if (prefix == null) {
+ prefix = "";
+ }
+ if (localName == null) {
+ localName = e.getNodeName();
+
+ if (localName == null) {
+ throw new IllegalStateException("Element's local name cannot be null!");
+ }
+ }
+
+ String decUri = writer.getNamespaceContext().getNamespaceURI(prefix);
+ boolean declareNamespace = decUri == null || !decUri.equals(ns);
+
+ if (ns == null || ns.isEmpty()) {
+ writer.writeStartElement(localName);
+ if (StringUtils.isEmpty(decUri)) {
+ declareNamespace = false;
+ }
+ } else {
+ writer.writeStartElement(prefix, localName, ns);
+ }
+
+ for (Node attr : sortElementAttributes(e.getAttributes())) {
+
+ String name = attr.getLocalName();
+ String attrPrefix = attr.getPrefix();
+ if (attrPrefix == null) {
+ attrPrefix = "";
+ }
+ if (name == null) {
+ name = attr.getNodeName();
+ }
+
+ if ("xmlns".equals(attrPrefix)) {
+ writer.writeNamespace(name, attr.getNodeValue());
+ writer.setPrefix(name, attr.getNodeValue());
+ if (name.equals(prefix) && attr.getNodeValue().equals(ns)) {
+ declareNamespace = false;
+ }
+ } else {
+ if ("xmlns".equals(name) && "".equals(attrPrefix)) {
+ writer.writeDefaultNamespace(attr.getNodeValue());
+ writer.setDefaultNamespace(attr.getNodeValue());
+ if (attr.getNodeValue().equals(ns)) {
+ declareNamespace = false;
+ } else if (StringUtils.isEmpty(attr.getNodeValue())
+ && StringUtils.isEmpty(ns)) {
+ declareNamespace = false;
+ }
+ } else {
+ String attns = attr.getNamespaceURI();
+ String value = attr.getNodeValue();
+ if (attns == null || attns.isEmpty()) {
+ writer.writeAttribute(name, value);
+ } else if (attrPrefix.isEmpty()) {
+ writer.writeAttribute(attns, name, value);
+ } else {
+ if (repairing && writer.getNamespaceContext().getNamespaceURI(attrPrefix) == null) {
+ writer.writeNamespace(attrPrefix, attns);
+ }
+ writer.writeAttribute(attrPrefix, attns, name, value);
+ }
+ }
+ }
+ }
+
+ if (declareNamespace && repairing) {
+ if (ns == null) {
+ writer.writeNamespace(prefix, "");
+ writer.setPrefix(prefix, "");
+ } else {
+ writer.writeNamespace(prefix, ns);
+ writer.setPrefix(prefix, ns);
+ }
+ }
+
+ Node nd = e.getFirstChild();
+ while (nd != null) {
+ writeNode(nd, writer, repairing);
+ nd = nd.getNextSibling();
+ }
+
+ if (endElement) {
+ writer.writeEndElement();
+ }
+ }
+
+ private static List<Node> sortElementAttributes(NamedNodeMap attrs) {
+ if (attrs.getLength() == 0) {
+ return Collections.<Node> emptyList();
+ }
+ List<Node> sortedAttrs = new ArrayList<>(attrs.getLength());
+ for (int i = 0; i < attrs.getLength(); i++) {
+ Node attr = attrs.item(i);
+ String name = attr.getLocalName();
+ if (name == null) {
+ name = attr.getNodeName();
+ }
+ if ("xmlns".equals(attr.getPrefix()) || "xmlns".equals(name)) {
+ sortedAttrs.add(0, attr);
+ } else {
+ sortedAttrs.add(attr);
+ }
+ }
+
+ return sortedAttrs;
+ }
+
+ public static void writeNode(Node n, XMLStreamWriter writer, boolean repairing)
+ throws XMLStreamException {
+
+ switch (n.getNodeType()) {
+ case Node.ELEMENT_NODE:
+ writeElement((Element)n, writer, repairing);
+ break;
+ case Node.TEXT_NODE:
+ writer.writeCharacters(((Text)n).getNodeValue());
+ break;
+ case Node.COMMENT_NODE:
+ writer.writeComment(((Comment)n).getData());
+ break;
+ case Node.CDATA_SECTION_NODE:
+ writer.writeCData(((CDATASection)n).getData());
+ break;
+ case Node.ENTITY_REFERENCE_NODE:
+ writer.writeEntityRef(((EntityReference)n).getNodeValue());
+ break;
+ case Node.PROCESSING_INSTRUCTION_NODE:
+ ProcessingInstruction pi = (ProcessingInstruction)n;
+ writer.writeProcessingInstruction(pi.getTarget(), pi.getData());
+ break;
+ case Node.DOCUMENT_NODE:
+ writeDocument((Document)n, writer, repairing);
+ break;
+ case Node.DOCUMENT_FRAGMENT_NODE: {
+ DocumentFragment frag = (DocumentFragment)n;
+ Node child = frag.getFirstChild();
+ while (child != null) {
+ writeNode(child, writer, repairing);
+ child = child.getNextSibling();
+ }
+ break;
+ }
+ case Node.DOCUMENT_TYPE_NODE:
+ try {
+ if (((DocumentType)n).getTextContent() != null) {
+ writer.writeDTD(((DocumentType)n).getTextContent());
+ }
+ } catch (UnsupportedOperationException ex) {
+ //can we ignore? DOM writers really don't allow this
+ //as there isn't a way to write a DTD in dom
+ }
+ break;
+ default:
+ throw new IllegalStateException("Found type: " + n.getClass().getName());
+ }
+ }
+
+ public static Document read(Source s) throws XMLStreamException {
+ XMLStreamReader reader = createXMLStreamReader(s);
+ try {
+ return read(reader);
+ } finally {
+ try {
+ reader.close();
+ } catch (Exception ex) {
+ //ignore
+ }
+ }
+ }
+ public static Document read(InputStream s) throws XMLStreamException {
+ XMLStreamReader reader = createXMLStreamReader(s);
+ try {
+ return read(reader);
+ } finally {
+ try {
+ reader.close();
+ } catch (Exception ex) {
+ //ignore
+ }
+ }
+ }
+ public static Document read(Reader s) throws XMLStreamException {
+ XMLStreamReader reader = createXMLStreamReader(s);
+ try {
+ return read(reader);
+ } finally {
+ try {
+ reader.close();
+ } catch (Exception ex) {
+ //ignore
+ }
+ }
+ }
+ public static Document read(File is) throws XMLStreamException, IOException {
+ try (InputStream fin = Files.newInputStream(is.toPath())) {
+ return read(fin);
+ }
+ }
+ public static Document read(InputSource s) throws XMLStreamException {
+ XMLStreamReader reader = null;
+ try {
+ reader = createXMLStreamReader(s);
+ return read(reader);
+ } finally {
+ StaxUtils.close(reader);
+ }
+ }
+ public static Document read(XMLStreamReader reader) throws XMLStreamException {
+ return read(reader, false);
+ }
+ public static Document read(XMLStreamReader reader, boolean recordLoc) throws XMLStreamException {
+ Document doc = DOMUtils.createDocument();
+ if (reader.getLocation().getSystemId() != null) {
+ try {
+ doc.setDocumentURI(reader.getLocation().getSystemId());
+ } catch (Exception e) {
+ //ignore - probably not DOM level 3
+ }
+ }
+ readDocElements(doc, doc, reader, true, recordLoc);
+ return doc;
+ }
+
+ public static Document read(DocumentBuilder builder, XMLStreamReader reader, boolean repairing)
+ throws XMLStreamException {
+
+ Document doc = builder == null ? DOMUtils.createDocument() : builder.newDocument();
+ if (reader.getLocation().getSystemId() != null) {
+ try {
+ doc.setDocumentURI(reader.getLocation().getSystemId());
+ } catch (Exception e) {
+ //ignore - probably not DOM level 3
+ }
+ }
+ readDocElements(doc, reader, repairing);
+ return doc;
+ }
+
+ /**
+ * @param parent
+ */
+ private static Document getDocument(Node parent) {
+ return (parent instanceof Document) ? (Document)parent : parent.getOwnerDocument();
+ }
+
+ private static boolean isDeclared(Element e, String namespaceURI, String prefix) {
+ while (e != null) {
+ Attr att;
+ if (prefix != null && !prefix.isEmpty()) {
+ att = e.getAttributeNodeNS(XML_NS, prefix);
+ } else {
+ att = e.getAttributeNode("xmlns");
+ }
+
+ if (att != null && att.getNodeValue().equals(namespaceURI)) {
+ return true;
+ }
+
+ if (e.getParentNode() instanceof Element) {
+ e = (Element)e.getParentNode();
+ } else if (StringUtils.isEmpty(prefix) && StringUtils.isEmpty(namespaceURI)) {
+ //A document that probably doesn't have any namespace qualifies elements
+ return true;
+ } else {
+ break;
+ }
+ }
+ return false;
+ }
+
+ public static void readDocElements(Node parent, XMLStreamReader reader, boolean repairing)
+ throws XMLStreamException {
+ Document doc = getDocument(parent);
+ readDocElements(doc, parent, reader, repairing, false);
+ }
+
+ public static void readDocElements(Node parent, XMLStreamReader reader, boolean repairing,
+ boolean isThreshold)
+ throws XMLStreamException {
+ Document doc = getDocument(parent);
+ readDocElements(doc, parent, reader, repairing, false, isThreshold);
+ }
+
+ /**
+ * @param parent
+ * @param reader
+ * @throws XMLStreamException
+ */
+ public static void readDocElements(Document doc, Node parent,
+ XMLStreamReader reader, boolean repairing, boolean recordLoc)
+ throws XMLStreamException {
+ readDocElements(doc, parent, reader, repairing, recordLoc, false);
+ }
+
+ /**
+ * @param parent
+ * @param reader
+ * @throws XMLStreamException
+ */
+ public static void readDocElements(Document doc, Node parent,
+ XMLStreamReader reader, boolean repairing, boolean recordLoc,
+ boolean isThreshold)
+ throws XMLStreamException {
+ final Deque<Node> stack = new ArrayDeque<>();
+ int event = reader.getEventType();
+ int elementCount = 0;
+ while (reader.hasNext()) {
+ switch (event) {
+ case XMLStreamConstants.START_ELEMENT: {
+ elementCount++;
+ Element e;
+ if (!StringUtils.isEmpty(reader.getPrefix())) {
+ e = doc.createElementNS(reader.getNamespaceURI(),
+ reader.getPrefix() + ':' + reader.getLocalName());
+ } else {
+ e = doc.createElementNS(reader.getNamespaceURI(), reader.getLocalName());
+ }
+ e = (Element)parent.appendChild(e);
+ recordLoc = addLocation(doc, e, reader, recordLoc);
+
+ for (int ns = 0; ns < reader.getNamespaceCount(); ns++) {
+ String uri = reader.getNamespaceURI(ns);
+ String prefix = reader.getNamespacePrefix(ns);
+
+ declare(e, uri, prefix);
+ }
+
+ for (int att = 0; att < reader.getAttributeCount(); att++) {
+ String name = reader.getAttributeLocalName(att);
+ String prefix = reader.getAttributePrefix(att);
+ if (prefix != null && !prefix.isEmpty()) {
+ name = prefix + ':' + name;
+ }
+
+ Attr attr = doc.createAttributeNS(reader.getAttributeNamespace(att), name);
+ attr.setValue(reader.getAttributeValue(att));
+ e.setAttributeNode(attr);
+ }
+
+ if (repairing && !isDeclared(e, reader.getNamespaceURI(), reader.getPrefix())) {
+ declare(e, reader.getNamespaceURI(), reader.getPrefix());
+ }
+ stack.push(parent);
+ if (isThreshold && MAX_ELEMENT_DEPTH_VAL != -1
+ && stack.size() >= MAX_ELEMENT_DEPTH_VAL) {
+ throw new DepthExceededStaxException("reach the innerElementLevelThreshold:"
+ + MAX_ELEMENT_DEPTH_VAL);
+ }
+ if (isThreshold && MAX_CHILD_ELEMENTS_VAL != -1
+ && elementCount >= MAX_CHILD_ELEMENTS_VAL) {
+ throw new DepthExceededStaxException("reach the innerElementCountThreshold:"
+ + MAX_CHILD_ELEMENTS_VAL);
+ }
+ parent = e;
+ break;
+ }
+ case XMLStreamConstants.END_ELEMENT:
+ if (stack.isEmpty()) {
+ return;
+ }
+ parent = stack.pop();
+ if (parent instanceof Document || parent instanceof DocumentFragment) {
+ return;
+ }
+ break;
+ case XMLStreamConstants.NAMESPACE:
+ break;
+ case XMLStreamConstants.ATTRIBUTE:
+ break;
+ case XMLStreamConstants.CHARACTERS:
+ if (parent != null) {
+ recordLoc = addLocation(doc,
+ parent.appendChild(doc.createTextNode(reader.getText())),
+ reader, recordLoc);
+ }
+ break;
+ case XMLStreamConstants.COMMENT:
+ if (parent != null) {
+ parent.appendChild(doc.createComment(reader.getText()));
+ }
+ break;
+ case XMLStreamConstants.CDATA:
+ recordLoc = addLocation(doc,
+ parent.appendChild(doc.createCDATASection(reader.getText())),
+ reader, recordLoc);
+ break;
+ case XMLStreamConstants.PROCESSING_INSTRUCTION:
+ parent.appendChild(doc.createProcessingInstruction(reader.getPITarget(), reader.getPIData()));
+ break;
+ case XMLStreamConstants.ENTITY_REFERENCE:
+ parent.appendChild(doc.createProcessingInstruction(reader.getPITarget(), reader.getPIData()));
+ break;
+ default:
+ break;
+ }
+
+ if (reader.hasNext()) {
+ event = reader.next();
+ }
+ }
+ }
+
+ public static class StreamToDOMContext {
+ private final Deque<Node> stack = new ArrayDeque<>();
+ private int elementCount;
+ private boolean repairing;
+ private boolean recordLoc;
+ private boolean threshold;
+
+ public StreamToDOMContext(boolean repairing, boolean recordLoc, boolean threshold) {
+ this.repairing = repairing;
+ this.recordLoc = recordLoc;
+ this.threshold = threshold;
+ }
+
+ public void setRecordLoc(boolean recordLoc) {
+ this.recordLoc = recordLoc;
+ }
+
+ public boolean isRecordLoc() {
+ return this.recordLoc;
+ }
+
+ public boolean isRepairing() {
+ return this.repairing;
+ }
+
+ public boolean isThreshold() {
+ return this.threshold;
+ }
+
+ public int incrementCount() {
+ return ++elementCount;
+ }
+
+ public int decreaseCount() {
+ return --elementCount;
+ }
+
+ public int getCount() {
+ return elementCount;
+ }
+
+ public void pushToStack(Node node) {
+ stack.push(node);
+ }
+
+ public Node popFromStack() {
+ return stack.pop();
+ }
+
+ public int getStackSize() {
+ return stack.size();
+ }
+
+ public boolean isStackEmpty() {
+ return stack.isEmpty();
+ }
+ }
+
+ public static void readDocElements(Document doc, Node parent, XMLStreamReader reader, StreamToDOMContext context)
+ throws XMLStreamException {
+ int event = reader.getEventType();
+ while (reader.hasNext()) {
+ switch (event) {
+ case XMLStreamConstants.START_ELEMENT: {
+ context.incrementCount();
+ Element e;
+ if (!StringUtils.isEmpty(reader.getPrefix())) {
+ e = doc.createElementNS(reader.getNamespaceURI(),
+ reader.getPrefix() + ":" + reader.getLocalName());
+ } else {
+ e = doc.createElementNS(reader.getNamespaceURI(), reader.getLocalName());
+ }
+ e = (Element)parent.appendChild(e);
+ if (context.isRecordLoc()) {
+ context.setRecordLoc(addLocation(doc, e, reader.getLocation(), context.isRecordLoc()));
+ }
+
+ for (int ns = 0; ns < reader.getNamespaceCount(); ns++) {
+ String uri = reader.getNamespaceURI(ns);
+ String prefix = reader.getNamespacePrefix(ns);
+
+ declare(e, uri, prefix);
+ }
+
+ for (int att = 0; att < reader.getAttributeCount(); att++) {
+ String name = reader.getAttributeLocalName(att);
+ String prefix = reader.getAttributePrefix(att);
+ if (prefix != null && prefix.length() > 0) {
+ name = prefix + ":" + name;
+ }
+
+ Attr attr = doc.createAttributeNS(reader.getAttributeNamespace(att), name);
+ attr.setValue(reader.getAttributeValue(att));
+ e.setAttributeNode(attr);
+ }
+
+ if (context.isRepairing() && !isDeclared(e, reader.getNamespaceURI(), reader.getPrefix())) {
+ declare(e, reader.getNamespaceURI(), reader.getPrefix());
+ }
+ context.pushToStack(parent);
+ if (context.isThreshold() && MAX_ELEMENT_DEPTH_VAL != -1
+ && context.getStackSize() >= MAX_ELEMENT_DEPTH_VAL) {
+ throw new DepthExceededStaxException("reach the innerElementLevelThreshold:"
+ + MAX_ELEMENT_DEPTH_VAL);
+ }
+ if (context.isThreshold() && MAX_CHILD_ELEMENTS_VAL != -1
+ && context.getCount() >= MAX_CHILD_ELEMENTS_VAL) {
+ throw new DepthExceededStaxException("reach the innerElementCountThreshold:"
+ + MAX_CHILD_ELEMENTS_VAL);
+ }
+ parent = e;
+ break;
+ }
+ case XMLStreamConstants.END_ELEMENT:
+ if (context.isStackEmpty()) {
+ return;
+ }
+ parent = context.popFromStack();
+ if (parent instanceof Document || parent instanceof DocumentFragment) {
+ return;
+ }
+ break;
+ case XMLStreamConstants.NAMESPACE:
+ break;
+ case XMLStreamConstants.ATTRIBUTE:
+ break;
+ case XMLStreamConstants.CHARACTERS:
+ if (parent != null) {
+ context.setRecordLoc(addLocation(doc,
+ parent.appendChild(doc.createTextNode(reader.getText())),
+ reader.getLocation(), context.isRecordLoc()));
+ }
+ break;
+ case XMLStreamConstants.COMMENT:
+ if (parent != null) {
+ parent.appendChild(doc.createComment(reader.getText()));
+ }
+ break;
+ case XMLStreamConstants.CDATA:
+ context.setRecordLoc(addLocation(doc,
+ parent.appendChild(doc.createCDATASection(reader.getText())),
+ reader.getLocation(), context.isRecordLoc()));
+ break;
+ case XMLStreamConstants.PROCESSING_INSTRUCTION:
+ parent.appendChild(doc.createProcessingInstruction(reader.getPITarget(), reader.getPIData()));
+ break;
+ case XMLStreamConstants.ENTITY_REFERENCE:
+ parent.appendChild(doc.createProcessingInstruction(reader.getPITarget(), reader.getPIData()));
+ break;
+ default:
+ break;
+ }
+
+ if (reader.hasNext()) {
+ event = reader.next();
+ }
+ }
+ }
+
+ public static Node readDocElement(Document doc, Node parent, XMLEvent ev, StreamToDOMContext context)
+ throws XMLStreamException {
+ switch (ev.getEventType()) {
+ case XMLStreamConstants.START_ELEMENT: {
+ context.incrementCount();
+ Element e;
+ StartElement startElem = ev.asStartElement();
+ QName name = startElem.getName();
+ if (!StringUtils.isEmpty(name.getPrefix())) {
+ e = doc.createElementNS(name.getNamespaceURI(),
+ name.getPrefix() + ":" + name.getLocalPart());
+ } else {
+ e = doc.createElementNS(name.getNamespaceURI(), name.getLocalPart());
+ }
+ e = (Element)parent.appendChild(e);
+ if (context.isRecordLoc()) {
+ context.setRecordLoc(addLocation(doc, e, startElem.getLocation(), context.isRecordLoc()));
+ }
+
+ if (context.isRepairing() && !isDeclared(e, name.getNamespaceURI(), name.getPrefix())) {
+ declare(e, name.getNamespaceURI(), name.getPrefix());
+ }
+ context.pushToStack(parent);
+ if (context.isThreshold() && MAX_ELEMENT_DEPTH_VAL != -1
+ && context.getStackSize() >= MAX_ELEMENT_DEPTH_VAL) {
+ throw new DepthExceededStaxException("reach the innerElementLevelThreshold:"
+ + MAX_ELEMENT_DEPTH_VAL);
+ }
+ if (context.isThreshold() && MAX_CHILD_ELEMENTS_VAL != -1
+ && context.getCount() >= MAX_CHILD_ELEMENTS_VAL) {
+ throw new DepthExceededStaxException("reach the innerElementCountThreshold:"
+ + MAX_CHILD_ELEMENTS_VAL);
+ }
+ parent = e;
+ break;
+ }
+ case XMLStreamConstants.END_ELEMENT:
+ if (context.isStackEmpty()) {
+ return parent;
+ }
+ parent = context.popFromStack();
+ if (parent instanceof Document || parent instanceof DocumentFragment) {
+ return parent;
+ }
+ break;
+ case XMLStreamConstants.NAMESPACE:
+ Namespace ns = (Namespace)ev;
+ declare((Element)parent, ns.getNamespaceURI(), ns.getPrefix());
+ break;
+ case XMLStreamConstants.ATTRIBUTE:
+ Attribute at = (Attribute)ev;
+ QName qname = at.getName();
+ String attName = qname.getLocalPart();
+ String attPrefix = qname.getPrefix();
+ if (attPrefix != null && attPrefix.length() > 0) {
+ attName = attPrefix + ":" + attName;
+ }
+ Attr attr = doc.createAttributeNS(qname.getNamespaceURI(), attName);
+ attr.setValue(at.getValue());
+ ((Element)parent).setAttributeNode(attr);
+ break;
+ case XMLStreamConstants.CHARACTERS:
+ Characters characters = ev.asCharacters();
+ context.setRecordLoc(addLocation(doc,
+ parent.appendChild(doc.createTextNode(characters.getData())),
+ characters.getLocation(), context.isRecordLoc()));
+ break;
+ case XMLStreamConstants.COMMENT:
+ parent.appendChild(doc.createComment(((javax.xml.stream.events.Comment)ev).getText()));
+ break;
+ case XMLStreamConstants.CDATA:
+ Characters cdata = ev.asCharacters();
+ context.setRecordLoc(addLocation(doc,
+ parent.appendChild(doc.createCDATASection(cdata.getData())),
+ cdata.getLocation(), context.isRecordLoc()));
+ break;
+ case XMLStreamConstants.PROCESSING_INSTRUCTION:
+ parent.appendChild(doc.createProcessingInstruction(((ProcessingInstruction)ev).getTarget(),
+ ((ProcessingInstruction)ev).getData()));
+ break;
+ case XMLStreamConstants.ENTITY_REFERENCE:
+ javax.xml.stream.events.EntityReference er = (javax.xml.stream.events.EntityReference)ev;
+ parent.appendChild(doc.createEntityReference(er.getName()));
+ break;
+ default:
+ break;
+ }
+ return parent;
+ }
+
+ private static boolean addLocation(Document doc, Node node,
+ Location loc,
+ boolean recordLoc) {
+ if (recordLoc && loc != null && (loc.getColumnNumber() != 0 || loc.getLineNumber() != 0)) {
+ try {
+ final int charOffset = loc.getCharacterOffset();
+ final int colNum = loc.getColumnNumber();
+ final int linNum = loc.getLineNumber();
+ final String pubId = loc.getPublicId() == null ? doc.getDocumentURI() : loc.getPublicId();
+ final String sysId = loc.getSystemId() == null ? doc.getDocumentURI() : loc.getSystemId();
+ Location loc2 = new Location() {
+ public int getCharacterOffset() {
+ return charOffset;
+ }
+ public int getColumnNumber() {
+ return colNum;
+ }
+ public int getLineNumber() {
+ return linNum;
+ }
+ public String getPublicId() {
+ return pubId;
+ }
+ public String getSystemId() {
+ return sysId;
+ }
+ };
+ node.setUserData("location", loc2, LocationUserDataHandler.INSTANCE);
+ } catch (Throwable ex) {
+ //possibly not DOM level 3, won't be able to record this then
+ return false;
+ }
+ }
+ return recordLoc;
+ }
+
+ private static boolean addLocation(Document doc, Node node,
+ XMLStreamReader reader,
+ boolean recordLoc) {
+ return addLocation(doc, node, reader.getLocation(), recordLoc);
+ }
+
+ private static class LocationUserDataHandler implements UserDataHandler {
+ public static final LocationUserDataHandler INSTANCE = new LocationUserDataHandler();
+
+ public void handle(short operation, String key, Object data, Node src, Node dst) {
+ if (operation == NODE_CLONED) {
+ dst.setUserData(key, data, this);
+ }
+ }
+ }
+
+ private static void declare(Element node, String uri, String prefix) {
+ String qualname;
+ if (prefix != null && prefix.length() > 0) {
+ qualname = "xmlns:" + prefix;
+ } else {
+ qualname = "xmlns";
+ }
+ Attr attr = node.getOwnerDocument().createAttributeNS(XML_NS, qualname);
+ attr.setValue(uri);
+ node.setAttributeNodeNS(attr);
+ }
+ public static XMLStreamReader createXMLStreamReader(InputSource src) {
+ String sysId = src.getSystemId() == null ? null : src.getSystemId();
+ String pubId = src.getPublicId() == null ? null : src.getPublicId();
+ if (src.getByteStream() != null) {
+ final InputStream is = src.getByteStream();
+
+ if (src.getEncoding() == null) {
+ final StreamSource ss = new StreamSource(is, sysId);
+ ss.setPublicId(pubId);
+
+ final XMLStreamReader xmlStreamReader = createXMLStreamReader(ss);
+ if (AUTO_CLOSE_INPUT_SOURCE) {
+ return new AutoCloseableXMLStreamReader(xmlStreamReader, is);
+ } else {
+ return xmlStreamReader;
+ }
+ }
+
+ return new AutoCloseableXMLStreamReader(createXMLStreamReader(is, src.getEncoding()), is);
+ } else if (src.getCharacterStream() != null) {
+ final Reader reader = src.getCharacterStream();
+ final StreamSource ss = new StreamSource(reader, sysId);
+ ss.setPublicId(pubId);
+ final XMLStreamReader xmlStreamReader = createXMLStreamReader(ss);
+ if (AUTO_CLOSE_INPUT_SOURCE) {
+ return new AutoCloseableXMLStreamReader(xmlStreamReader, reader);
+ } else {
+ return xmlStreamReader;
+ }
+ } else {
+ try {
+ final URL url = new URL(sysId);
+ final InputStream is = url.openStream();
+ final StreamSource ss = new StreamSource(is, sysId);
+ ss.setPublicId(pubId);
+ return new AutoCloseableXMLStreamReader(createXMLStreamReader(ss), is);
+ } catch (Exception ex) {
+ //ignore - not a valid URL
+ }
+ }
+ throw new IllegalArgumentException("InputSource must have a ByteStream or CharacterStream");
+ }
+ /**
+ * @param in
+ * @param encoding
+ */
+ public static XMLStreamReader createXMLStreamReader(InputStream in, String encoding) {
+ XMLInputFactory factory = getXMLInputFactory();
+ try {
+ return factory.createXMLStreamReader(in, encoding != null ? encoding : StandardCharsets.UTF_8.name());
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Couldn't parse stream.", e);
+ } finally {
+ returnXMLInputFactory(factory);
+ }
+ }
+
+ /**
+ * @param in
+ */
+ public static XMLStreamReader createXMLStreamReader(InputStream in) {
+ XMLInputFactory factory = getXMLInputFactory();
+ try {
+ return factory.createXMLStreamReader(in);
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Couldn't parse stream.", e);
+ } finally {
+ returnXMLInputFactory(factory);
+ }
+ }
+ public static XMLStreamReader createXMLStreamReader(String systemId, InputStream in) {
+ XMLInputFactory factory = getXMLInputFactory();
+ try {
+ return factory.createXMLStreamReader(systemId, in);
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Couldn't parse stream.", e);
+ } finally {
+ returnXMLInputFactory(factory);
+ }
+ }
+
+ public static XMLStreamReader createXMLStreamReader(Element el) {
+ return new W3CDOMStreamReader(el);
+ }
+ public static XMLStreamReader createXMLStreamReader(Document doc) {
+ return new W3CDOMStreamReader(doc.getDocumentElement());
+ }
+ public static XMLStreamReader createXMLStreamReader(Element el, String sysId) {
+ return new W3CDOMStreamReader(el, sysId);
+ }
+ public static XMLStreamReader createXMLStreamReader(Document doc, String sysId) {
+ return new W3CDOMStreamReader(doc.getDocumentElement(), sysId);
+ }
+
+ public static XMLStreamReader createXMLStreamReader(Source source) {
+ try {
+ if (source instanceof DOMSource) {
+ DOMSource ds = (DOMSource)source;
+ Node nd = ds.getNode();
+ Element el = null;
+ if (nd instanceof Document) {
+ el = ((Document)nd).getDocumentElement();
+ } else if (nd instanceof Element) {
+ el = (Element)nd;
+ }
+
+ if (null != el) {
+ return new W3CDOMStreamReader(el, source.getSystemId());
+ }
+ } else if (source instanceof StAXSource) {
+ return ((StAXSource)source).getXMLStreamReader();
+ } else if (source instanceof StaxSource) {
+ return ((StaxSource)source).getXMLStreamReader();
+ } else if (source instanceof SAXSource) {
+ SAXSource ss = (SAXSource)source;
+ if (ss.getXMLReader() == null) {
+ return createXMLStreamReader(((SAXSource)source).getInputSource());
+ }
+ }
+
+ XMLInputFactory factory = getXMLInputFactory();
+ try {
+ XMLStreamReader reader = null;
+
+ try {
+ reader = factory.createXMLStreamReader(source);
+ } catch (UnsupportedOperationException e) {
+ //ignore
+ }
+ if (reader == null && source instanceof StreamSource) {
+ //createXMLStreamReader from Source is optional, we'll try and map it
+ StreamSource ss = (StreamSource)source;
+ if (ss.getInputStream() != null) {
+ reader = factory.createXMLStreamReader(ss.getSystemId(),
+ ss.getInputStream());
+ } else {
+ reader = factory.createXMLStreamReader(ss.getSystemId(),
+ ss.getReader());
+ }
+ }
+ return reader;
+ } finally {
+ returnXMLInputFactory(factory);
+ }
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Couldn't parse stream.", e);
+ }
+ }
+
+ /**
+ * @param reader
+ */
+ public static XMLStreamReader createXMLStreamReader(Reader reader) {
+ XMLInputFactory factory = getXMLInputFactory();
+ try {
+ return factory.createXMLStreamReader(reader);
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Couldn't parse stream.", e);
+ } finally {
+ returnXMLInputFactory(factory);
+ }
+ }
+
+ /**
+ * Reads a QName from the element text. Reader must be positioned at the
+ * start tag.
+ */
+ public static QName readQName(XMLStreamReader reader) throws XMLStreamException {
+ String value = reader.getElementText();
+ if (value == null) {
+ return null;
+ }
+ value = value.trim();
+
+ int index = value.indexOf(':');
+
+ if (index == -1) {
+ return new QName(value);
+ }
+
+ String prefix = value.substring(0, index);
+ String localName = value.substring(index + 1);
+ String ns = reader.getNamespaceURI(prefix);
+
+ if (!StringUtils.isEmpty(prefix) && ns == null) {
+ throw new RuntimeException("Invalid QName in mapping: " + value);
+ }
+
+ if (ns == null) {
+ return new QName(localName);
+ }
+
+ return new QName(ns, localName, prefix);
+ }
+
+ /**
+ * Create a unique namespace uri/prefix combination.
+ *
+ * @return The namespace with the specified URI. If one doesn't exist, one
+ * is created.
+ * @throws XMLStreamException
+ */
+ public static String getUniquePrefix(XMLStreamWriter writer, String namespaceURI, boolean declare)
+ throws XMLStreamException {
+ String prefix = writer.getPrefix(namespaceURI);
+ if (prefix == null) {
+ prefix = getUniquePrefix(writer);
+
+ if (declare) {
+ writer.setPrefix(prefix, namespaceURI);
+ writer.writeNamespace(prefix, namespaceURI);
+ }
+ }
+ return prefix;
+ }
+ public static String getUniquePrefix(XMLStreamWriter writer, String namespaceURI)
+ throws XMLStreamException {
+ return getUniquePrefix(writer, namespaceURI, false);
+ }
+ public static String getUniquePrefix(XMLStreamWriter writer) {
+ NamespaceContext nc = writer.getNamespaceContext();
+ if (nc == null) {
+ return DEF_PREFIXES[0];
+ }
+ for (String t : DEF_PREFIXES) {
+ String uri = nc.getNamespaceURI(t);
+ if (StringUtils.isEmpty(uri)) {
+ return t;
+ }
+ }
+
+ int n = 10;
+ while (true) {
+ String nsPrefix = "ns" + n;
+ String uri = nc.getNamespaceURI(nsPrefix);
+ if (StringUtils.isEmpty(uri)) {
+ return nsPrefix;
+ }
+ n++;
+ }
+ }
+
+
+ public static void printXmlFragment(XMLStreamReader reader) {
+ try {
+ StringWriter sw = new StringWriter(1024);
+ XMLStreamWriter writer = null;
+ try {
+ writer = new PrettyPrintXMLStreamWriter(createXMLStreamWriter(sw), 4);
+ copy(reader, writer);
+ writer.flush();
+ } finally {
+ StaxUtils.close(writer);
+ }
+ LOG.info(sw.toString());
+ } catch (XMLStreamException e) {
+ LOG.severe(e.getMessage());
+ }
+ }
+
+
+ private static void writeStartElementEvent(XMLEvent event, XMLStreamWriter writer)
+ throws XMLStreamException {
+ StartElement start = event.asStartElement();
+ QName name = start.getName();
+ String nsURI = name.getNamespaceURI();
+ String localName = name.getLocalPart();
+ String prefix = name.getPrefix();
+
+ if (prefix != null) {
+ writer.writeStartElement(prefix, localName, nsURI);
+ } else if (nsURI != null) {
+ writer.writeStartElement(localName, nsURI);
+ } else {
+ writer.writeStartElement(localName);
+ }
+ Iterator<XMLEvent> it = CastUtils.cast(start.getNamespaces());
+ while (it != null && it.hasNext()) {
+ writeEvent(it.next(), writer);
+ }
+
+ it = CastUtils.cast(start.getAttributes());
+ while (it != null && it.hasNext()) {
+ writeAttributeEvent(it.next(), writer);
+ }
+ }
+ private static void writeAttributeEvent(XMLEvent event, XMLStreamWriter writer)
+ throws XMLStreamException {
+
+ Attribute attr = (Attribute)event;
+ QName name = attr.getName();
+ String nsURI = name.getNamespaceURI();
+ String localName = name.getLocalPart();
+ String prefix = name.getPrefix();
+ String value = attr.getValue();
+
+ if (prefix != null) {
+ writer.writeAttribute(prefix, nsURI, localName, value);
+ } else if (nsURI != null) {
+ writer.writeAttribute(nsURI, localName, value);
+ } else {
+ writer.writeAttribute(localName, value);
+ }
+ }
+
+ public static void writeEvent(XMLEvent event, XMLStreamWriter writer)
+ throws XMLStreamException {
+
+ switch (event.getEventType()) {
+ case XMLStreamConstants.START_ELEMENT:
+ writeStartElementEvent(event, writer);
+ break;
+ case XMLStreamConstants.END_ELEMENT:
+ writer.writeEndElement();
+ break;
+ case XMLStreamConstants.ATTRIBUTE:
+ writeAttributeEvent(event, writer);
+ break;
+ case XMLStreamConstants.ENTITY_REFERENCE:
+ writer.writeEntityRef(((javax.xml.stream.events.EntityReference)event).getName());
+ break;
+ case XMLStreamConstants.DTD:
+ writer.writeDTD(((DTD)event).getDocumentTypeDeclaration());
+ break;
+ case XMLStreamConstants.PROCESSING_INSTRUCTION:
+ if (((javax.xml.stream.events.ProcessingInstruction)event).getData() != null) {
+ writer.writeProcessingInstruction(
+ ((javax.xml.stream.events.ProcessingInstruction)event).getTarget(),
+ ((javax.xml.stream.events.ProcessingInstruction)event).getData());
+ } else {
+ writer.writeProcessingInstruction(
+ ((javax.xml.stream.events.ProcessingInstruction)event).getTarget());
+ }
+ break;
+ case XMLStreamConstants.NAMESPACE:
+ if (((Namespace)event).isDefaultNamespaceDeclaration()) {
+ writer.writeDefaultNamespace(((Namespace)event).getNamespaceURI());
+ writer.setDefaultNamespace(((Namespace)event).getNamespaceURI());
+ } else {
+ writer.writeNamespace(((Namespace)event).getPrefix(),
+ ((Namespace)event).getNamespaceURI());
+ writer.setPrefix(((Namespace)event).getPrefix(),
+ ((Namespace)event).getNamespaceURI());
+ }
+ break;
+ case XMLStreamConstants.COMMENT:
+ writer.writeComment(((javax.xml.stream.events.Comment)event).getText());
+ break;
+ case XMLStreamConstants.CHARACTERS:
+ case XMLStreamConstants.SPACE:
+ writer.writeCharacters(event.asCharacters().getData());
+ break;
+ case XMLStreamConstants.CDATA:
+ writer.writeCData(event.asCharacters().getData());
+ break;
+ case XMLStreamConstants.START_DOCUMENT:
+ if (((StartDocument)event).encodingSet()) {
+ writer.writeStartDocument(((StartDocument)event).getCharacterEncodingScheme(),
+ ((StartDocument)event).getVersion());
+
+ } else {
+ writer.writeStartDocument(((StartDocument)event).getVersion());
+ }
+ break;
+ case XMLStreamConstants.END_DOCUMENT:
+ writer.writeEndDocument();
+ break;
+ default:
+ //shouldn't get here
+ }
+ }
+ public static void print(Node node) {
+ XMLStreamWriter writer = null;
+ try {
+ writer = createXMLStreamWriter(System.out);
+ copy(new DOMSource(node), writer);
+ writer.flush();
+ } catch (XMLStreamException e) {
+ throw new RuntimeException(e);
+ } finally {
+ StaxUtils.close(writer);
+ }
+ }
+
+ public static String toString(Source src) {
+ StringWriter sw = new StringWriter(1024);
+ XMLStreamWriter writer = null;
+ try {
+ writer = createXMLStreamWriter(sw);
+ copy(src, writer);
+ writer.flush();
+ } catch (XMLStreamException e) {
+ throw new RuntimeException(e);
+ } finally {
+ StaxUtils.close(writer);
+ }
+ return sw.toString();
+ }
+ public static String toString(Node src) {
+ return toString(new DOMSource(src));
+ }
+ public static String toString(Document doc) {
+ StringWriter sw = new StringWriter(1024);
+ XMLStreamWriter writer = null;
+ try {
+ writer = createXMLStreamWriter(sw);
+ copy(doc, writer);
+ writer.flush();
+ } catch (XMLStreamException e) {
+ throw new RuntimeException(e);
+ } finally {
+ StaxUtils.close(writer);
+ }
+ return sw.toString();
+ }
+ public static String toString(Element el) {
+ return toString(el, 0);
+ }
+ public static String toString(Element el, int indent) {
+ StringWriter sw = new StringWriter(1024);
+ XMLStreamWriter writer = null;
+ try {
+ writer = createXMLStreamWriter(sw);
+ if (indent > 0) {
+ writer = new PrettyPrintXMLStreamWriter(writer, indent);
+ }
+ copy(el, writer);
+ writer.flush();
+ } catch (XMLStreamException e) {
+ throw new RuntimeException(e);
+ } finally {
+ StaxUtils.close(writer);
+ }
+ return sw.toString();
+ }
+ public static void close(XMLStreamReader reader) throws XMLStreamException {
+ if (reader != null) {
+ reader.close();
+ }
+ }
+
+ public static void close(XMLStreamWriter writer) {
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (Exception e) {
+ //ignore
+ }
+ }
+ }
+
+ public static boolean isSecureReader(XMLStreamReader reader, Message message) {
+ if (reader instanceof DocumentDepthProperties) {
+ return true;
+ }
+ try {
+ if (reader.getProperty(P_MAX_CHILDREN_PER_ELEMENT) != null) {
+ return true;
+ }
+ } catch (Exception ex) {
+ //ignore
+ }
+ return false;
+ }
+
+ public static XMLStreamReader configureReader(XMLStreamReader xreader, Message message) throws XMLStreamException {
+ Integer messageMaxChildElements = PropertyUtils.getInteger(message, MAX_CHILD_ELEMENTS);
+ Integer messageMaxElementDepth = PropertyUtils.getInteger(message, MAX_ELEMENT_DEPTH);
+ Integer messageMaxAttributeCount = PropertyUtils.getInteger(message, MAX_ATTRIBUTE_COUNT);
+ Integer messageMaxAttributeSize = PropertyUtils.getInteger(message, MAX_ATTRIBUTE_SIZE);
+ Integer messageMaxTextLength = PropertyUtils.getInteger(message, MAX_TEXT_LENGTH);
+ Long messageMaxElementCount = PropertyUtils.getLong(message, MAX_ELEMENT_COUNT);
+ Long messageMaxXMLCharacters = PropertyUtils.getLong(message, MAX_XML_CHARACTERS);
+ return configureReader(xreader, messageMaxChildElements, messageMaxElementDepth,
+ messageMaxAttributeCount, messageMaxAttributeSize, messageMaxTextLength,
+ messageMaxElementCount, messageMaxXMLCharacters);
+ }
+
+ //CHECKSTYLE:OFF - lots of params to configure
+ public static XMLStreamReader configureReader(XMLStreamReader reader, Integer maxChildElements,
+ Integer maxElementDepth, Integer maxAttributeCount,
+ Integer maxAttributeSize, Integer maxTextLength,
+ Long maxElementCount, Long maxXMLCharacters)
+ throws XMLStreamException {
+ //CHECKSTYLE:ON
+
+ // We currently ONLY support Woodstox 4.2.x for most of this other than a few things
+ // that we can handle via a wrapper.
+ try {
+ DocumentDepthProperties p = null;
+ if (maxChildElements != null) {
+ try {
+ setProperty(reader, P_MAX_CHILDREN_PER_ELEMENT, maxChildElements);
+ } catch (Throwable t) {
+ //we can handle this via a wrapper
+ p = new DocumentDepthProperties();
+ p.setInnerElementCountThreshold(maxChildElements);
+ }
+ }
+ if (maxElementDepth != null) {
+ try {
+ setProperty(reader, P_MAX_ELEMENT_DEPTH, maxElementDepth);
+ } catch (Throwable t) {
+ //we can handle this via a wrapper
+ if (p == null) {
+ p = new DocumentDepthProperties();
+ }
+ p.setInnerElementLevelThreshold(maxElementDepth);
+ }
+ }
+ if (maxAttributeCount != null) {
+ setProperty(reader, P_MAX_ATTRIBUTES_PER_ELEMENT, maxAttributeCount);
+ }
+ if (maxAttributeSize != null) {
+ setProperty(reader, P_MAX_ATTRIBUTE_SIZE, maxAttributeSize);
+ }
+ if (maxTextLength != null) {
+ setProperty(reader, P_MAX_TEXT_LENGTH, maxTextLength);
+ }
+ if (maxElementCount != null) {
+ try {
+ setProperty(reader, P_MAX_ELEMENT_COUNT, maxElementCount);
+ } catch (Throwable t) {
+ //we can handle this via a wrapper
+ if (p == null) {
+ p = new DocumentDepthProperties();
+ }
+ p.setElementCountThreshold(maxElementCount.intValue());
+ }
+ }
+ if (maxXMLCharacters != null) {
+ setProperty(reader, P_MAX_CHARACTERS, maxXMLCharacters);
+ }
+ if (p != null) {
+ reader = new DepthRestrictingStreamReader(reader, p);
+ }
+ } catch (ClassCastException cce) {
+ //not an XMLStreamReader2
+ if (ALLOW_INSECURE_PARSER_VAL) {
+ LOG.warning("INSTANCE_NOT_XMLSTREAMREADER2");
+ } else {
+ throw new XMLStreamException(cce.getMessage(), cce);
+ }
+ } catch (IllegalArgumentException cce) {
+ //not a property supported by this version of woodstox
+ if (ALLOW_INSECURE_PARSER_VAL) {
+ LOG.log(Level.WARNING, "SECURE_PROPERTY_NOT_SUPPORTED", cce.getMessage());
+ } else {
+ throw new XMLStreamException(cce.getMessage(), cce);
+ }
+ }
+ return reader;
+ }
+ private static void setProperty(XMLStreamReader reader, String p, Object v) {
+ WoodstoxHelper.setProperty(reader, p, v);
+ }
+
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/staxutils/W3CDOMStreamReader.java b/transform/src/patch/java/org/apache/cxf/staxutils/W3CDOMStreamReader.java
new file mode 100644
index 0000000..03b1992
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/staxutils/W3CDOMStreamReader.java
@@ -0,0 +1,429 @@
+/**
+ * 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.staxutils;
+
+import java.util.ArrayList;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+import javax.xml.stream.Location;
+import javax.xml.stream.XMLStreamException;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Comment;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentFragment;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.ProcessingInstruction;
+import org.w3c.dom.Text;
+import org.w3c.dom.TypeInfo;
+
+import org.apache.cxf.helpers.DOMUtils;
+
+public class W3CDOMStreamReader extends AbstractDOMStreamReader<Node, Node> {
+ private Node content;
+
+ private Document document;
+
+ private W3CNamespaceContext context;
+
+ private String sysId;
+
+ /**
+ * @param element
+ */
+ public W3CDOMStreamReader(Element element) {
+ super(new ElementFrame<Node, Node>(element, null));
+ content = element;
+ newFrame(getCurrentFrame());
+
+ this.document = element.getOwnerDocument();
+ }
+ public W3CDOMStreamReader(Element element, String systemId) {
+ this(element);
+ sysId = systemId;
+ }
+ public W3CDOMStreamReader(Document doc) {
+ super(new ElementFrame<Node, Node>(doc, false) {
+ public boolean isDocument() {
+ return true;
+ }
+ });
+ this.document = doc;
+ }
+ public W3CDOMStreamReader(DocumentFragment docfrag) {
+ super(new ElementFrame<Node, Node>(docfrag, true) {
+ public boolean isDocumentFragment() {
+ return true;
+ }
+ });
+ this.document = docfrag.getOwnerDocument();
+ }
+
+ /**
+ * Get the document associated with this stream.
+ */
+ public Document getDocument() {
+ return document;
+ }
+ public String getSystemId() {
+ try {
+ return sysId == null ? document.getDocumentURI() : sysId;
+ } catch (Throwable ex) {
+ //ignore, probably not DOM level 3
+ }
+ return sysId;
+ }
+ /**
+ * Find name spaces declaration in atrributes and move them to separate
+ * collection.
+ */
+ @Override
+ protected final void newFrame(ElementFrame<Node, Node> frame) {
+ Node element = getCurrentNode();
+ frame.uris = new ArrayList<>();
+ frame.prefixes = new ArrayList<>();
+ frame.attributes = new ArrayList<>();
+
+ if (context == null) {
+ context = new W3CNamespaceContext();
+ }
+ if (element instanceof Element) {
+ context.setElement((Element)element);
+ }
+
+ NamedNodeMap nodes = element.getAttributes();
+
+ if (nodes != null) {
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Node node = nodes.item(i);
+ String prefix = node.getPrefix();
+ String localName = node.getLocalName();
+ String value = node.getNodeValue();
+ String name = node.getNodeName();
+
+ if (prefix == null) {
+ prefix = "";
+ }
+
+ if ("xmlns".equals(name)) {
+ frame.uris.add(value);
+ frame.prefixes.add("");
+ } else if (!prefix.isEmpty() && "xmlns".equals(prefix)) {
+ frame.uris.add(value);
+ frame.prefixes.add(localName);
+ } else if (name.startsWith("xmlns:")) {
+ frame.uris.add(value);
+ frame.prefixes.add(name.substring(6));
+ } else {
+ frame.attributes.add(node);
+ }
+ }
+ }
+ }
+
+ public final Node getCurrentNode() {
+ return getCurrentFrame().element;
+ }
+ public final Element getCurrentElement() {
+ return (Element)getCurrentFrame().element;
+ }
+
+ @Override
+ protected ElementFrame<Node, Node> getChildFrame() {
+ return new ElementFrame<Node, Node>(getCurrentFrame().currentChild,
+ getCurrentFrame());
+ }
+
+ @Override
+ protected boolean hasMoreChildren() {
+ if (getCurrentFrame().currentChild == null) {
+ return getCurrentNode().getFirstChild() != null;
+ }
+ return getCurrentFrame().currentChild.getNextSibling() != null;
+ }
+
+ @Override
+ protected int nextChild() {
+ ElementFrame<Node, Node> frame = getCurrentFrame();
+ if (frame.currentChild == null) {
+ content = getCurrentNode().getFirstChild();
+ } else {
+ content = frame.currentChild.getNextSibling();
+ }
+
+ frame.currentChild = content;
+ switch (content.getNodeType()) {
+ case Node.ELEMENT_NODE:
+ return START_ELEMENT;
+ case Node.TEXT_NODE:
+ return CHARACTERS;
+ case Node.COMMENT_NODE:
+ return COMMENT;
+ case Node.CDATA_SECTION_NODE:
+ return CDATA;
+ case Node.ENTITY_REFERENCE_NODE:
+ return ENTITY_REFERENCE;
+ case Node.PROCESSING_INSTRUCTION_NODE:
+ return PROCESSING_INSTRUCTION;
+ default:
+ throw new IllegalStateException("Found type: " + content.getClass().getName());
+ }
+ }
+
+ @Override
+ public String getElementText() throws XMLStreamException {
+ String result = DOMUtils.getRawContent(content);
+
+ ElementFrame<Node, Node> frame = getCurrentFrame();
+ frame.ended = true;
+ currentEvent = END_ELEMENT;
+ endElement();
+
+ // we should not return null according to the StAx API javadoc
+ return result != null ? result : "";
+ }
+
+ @Override
+ public String getNamespaceURI(String prefix) {
+ ElementFrame<Node, Node> frame = getCurrentFrame();
+
+ while (null != frame) {
+ int index = frame.prefixes.indexOf(prefix);
+ if (index != -1) {
+ return frame.uris.get(index);
+ }
+
+ if (frame.parent == null && frame.getElement() instanceof Element) {
+ return ((Element)frame.getElement()).lookupNamespaceURI(prefix);
+ }
+ frame = frame.parent;
+ }
+
+ return null;
+ }
+
+ public String getAttributeValue(String ns, String local) {
+ Attr at;
+ if (ns == null || ns.isEmpty()) {
+ at = getCurrentElement().getAttributeNode(local);
+ } else {
+ at = getCurrentElement().getAttributeNodeNS(ns, local);
+ }
+
+ if (at == null) {
+ return null;
+ }
+ return at.getNodeValue();
+ }
+
+ public int getAttributeCount() {
+ return getCurrentFrame().attributes.size();
+ }
+
+ Attr getAttribute(int i) {
+ return (Attr)getCurrentFrame().attributes.get(i);
+ }
+
+ private String getLocalName(Attr attr) {
+
+ String name = attr.getLocalName();
+ if (name == null) {
+ name = attr.getNodeName();
+ }
+ return name;
+ }
+
+ public QName getAttributeName(int i) {
+ Attr at = getAttribute(i);
+
+ String prefix = at.getPrefix();
+ String ln = getLocalName(at);
+ // at.getNodeName();
+ String ns = at.getNamespaceURI();
+
+ if (prefix == null) {
+ return new QName(ns, ln);
+ }
+ return new QName(ns, ln, prefix);
+ }
+
+ public String getAttributeNamespace(int i) {
+ return getAttribute(i).getNamespaceURI();
+ }
+
+ public String getAttributeLocalName(int i) {
+ Attr attr = getAttribute(i);
+ return getLocalName(attr);
+ }
+
+ public String getAttributePrefix(int i) {
+ return getAttribute(i).getPrefix();
+ }
+
+ public String getAttributeType(int i) {
+ Attr attr = getAttribute(i);
+ if (attr.isId()) {
+ return "ID";
+ }
+ TypeInfo schemaType = null;
+ try {
+ schemaType = attr.getSchemaTypeInfo();
+ } catch (Throwable t) {
+ //DOM level 2?
+ }
+ return (schemaType == null) ? "CDATA"
+ : schemaType.getTypeName() == null ? "CDATA" : schemaType.getTypeName();
+ }
+
+
+ public String getAttributeValue(int i) {
+ return getAttribute(i).getValue();
+ }
+
+ public boolean isAttributeSpecified(int i) {
+ return getAttribute(i).getValue() != null;
+ }
+
+ public int getNamespaceCount() {
+ return getCurrentFrame().prefixes.size();
+ }
+
+ public String getNamespacePrefix(int i) {
+ return getCurrentFrame().prefixes.get(i);
+ }
+
+ public String getNamespaceURI(int i) {
+ return getCurrentFrame().uris.get(i);
+ }
+
+ public NamespaceContext getNamespaceContext() {
+ return context;
+ }
+
+ public String getText() {
+ if (content instanceof Text) {
+ return ((Text)content).getData();
+ } else if (content instanceof Comment) {
+ return ((Comment)content).getData();
+ }
+ return DOMUtils.getRawContent(getCurrentNode());
+ }
+
+ public char[] getTextCharacters() {
+ return getText().toCharArray();
+ }
+
+ public int getTextStart() {
+ return 0;
+ }
+
+ public int getTextLength() {
+ return getText().length();
+ }
+
+ public String getEncoding() {
+ return null;
+ }
+
+ public QName getName() {
+ Node el = getCurrentNode();
+
+ String prefix = getPrefix();
+ String ln = getLocalName();
+
+ return new QName(el.getNamespaceURI(), ln, prefix);
+ }
+
+ public String getLocalName() {
+ String ln = getCurrentNode().getLocalName();
+ if (ln == null) {
+ ln = getCurrentNode().getNodeName();
+ if (ln.indexOf(':') != -1) {
+ ln = ln.substring(ln.indexOf(':') + 1);
+ }
+ }
+ return ln;
+ }
+
+ public String getNamespaceURI() {
+ String ln = getCurrentNode().getLocalName();
+ if (ln == null) {
+ ln = getCurrentNode().getNodeName();
+ if (ln.indexOf(':') == -1) {
+ ln = getNamespaceURI("");
+ } else {
+ ln = getNamespaceURI(ln.substring(0, ln.indexOf(':')));
+ }
+ return ln;
+ }
+ return getCurrentNode().getNamespaceURI();
+ }
+
+ public String getPrefix() {
+ String prefix = getCurrentNode().getPrefix();
+ if (prefix == null) {
+ String nodeName = getCurrentNode().getNodeName();
+ if (nodeName.indexOf(':') != -1) {
+ prefix = nodeName.substring(0, nodeName.indexOf(':'));
+ } else {
+ prefix = "";
+ }
+ }
+ return prefix;
+ }
+
+ public String getPITarget() {
+ return ((ProcessingInstruction)content).getTarget();
+ }
+
+ public String getPIData() {
+ return ((ProcessingInstruction)content).getData();
+ }
+ public Location getLocation() {
+ try {
+ Object o = getCurrentNode().getUserData("location");
+ if (o instanceof Location) {
+ return (Location)o;
+ }
+ } catch (Throwable ex) {
+ //ignore, probably not DOM level 3
+ }
+ return super.getLocation();
+ }
+
+ public String toString() {
+ if (document == null) {
+ return "<null>";
+ }
+ if (document.getDocumentElement() == null) {
+ return "<null document element>";
+ }
+ try {
+ return StaxUtils.toString(document);
+ } catch (Throwable t) {
+ t.printStackTrace();
+ return super.toString();
+ }
+ }
+
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/transport/ChainInitiationObserver.java b/transform/src/patch/java/org/apache/cxf/transport/ChainInitiationObserver.java
new file mode 100644
index 0000000..a4b8aa3
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/transport/ChainInitiationObserver.java
@@ -0,0 +1,197 @@
+/**
+ * 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.transport;
+
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collection;
+
+import javax.xml.namespace.QName;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.binding.Binding;
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.classloader.ClassLoaderUtils.ClassLoaderHolder;
+import org.apache.cxf.endpoint.Endpoint;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.interceptor.Interceptor;
+import org.apache.cxf.interceptor.InterceptorChain;
+import org.apache.cxf.interceptor.InterceptorProvider;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.ExchangeImpl;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.PhaseChainCache;
+import org.apache.cxf.phase.PhaseManager;
+import org.apache.cxf.service.Service;
+import org.apache.cxf.service.model.EndpointInfo;
+
+public class ChainInitiationObserver implements MessageObserver {
+ protected Endpoint endpoint;
+ protected Bus bus;
+ protected ClassLoader loader;
+
+ private PhaseChainCache chainCache = new PhaseChainCache();
+
+ public ChainInitiationObserver(Endpoint endpoint, Bus bus) {
+ super();
+ this.endpoint = endpoint;
+ this.bus = bus;
+ if (bus != null) {
+ loader = bus.getExtension(ClassLoader.class);
+ }
+ }
+
+ public void onMessage(Message m) {
+ Bus origBus = BusFactory.getAndSetThreadDefaultBus(bus);
+ ClassLoaderHolder origLoader = null;
+ try {
+ if (loader != null) {
+ origLoader = ClassLoaderUtils.setThreadContextClassloader(loader);
+ }
+ InterceptorChain phaseChain;
+
+ if (m.getInterceptorChain() != null) {
+ phaseChain = m.getInterceptorChain();
+ // To make sure the phase chain is run by one thread once
+ synchronized (phaseChain) {
+ if (phaseChain.getState() == InterceptorChain.State.PAUSED
+ || phaseChain.getState() == InterceptorChain.State.SUSPENDED) {
+ phaseChain.resume();
+ return;
+ }
+ }
+ }
+
+ Message message = getBinding().createMessage(m);
+ Exchange exchange = message.getExchange();
+ if (exchange == null) {
+ exchange = new ExchangeImpl();
+ m.setExchange(exchange);
+ }
+ exchange.setInMessage(message);
+ setExchangeProperties(exchange, message);
+
+ InterceptorProvider dbp = null;
+ if (endpoint.getService().getDataBinding() instanceof InterceptorProvider) {
+ dbp = (InterceptorProvider)endpoint.getService().getDataBinding();
+ }
+ // setup chain
+ if (dbp == null) {
+ phaseChain = chainCache.get(bus.getExtension(PhaseManager.class).getInPhases(),
+ bus.getInInterceptors(),
+ endpoint.getService().getInInterceptors(),
+ endpoint.getInInterceptors(),
+ getBinding().getInInterceptors());
+ } else {
+ phaseChain = chainCache.get(bus.getExtension(PhaseManager.class).getInPhases(),
+ bus.getInInterceptors(),
+ endpoint.getService().getInInterceptors(),
+ endpoint.getInInterceptors(),
+ getBinding().getInInterceptors(),
+ dbp.getInInterceptors());
+ }
+
+
+
+ message.setInterceptorChain(phaseChain);
+
+ phaseChain.setFaultObserver(endpoint.getOutFaultObserver());
+
+ addToChain(phaseChain, message);
+
+ phaseChain.doIntercept(message);
+
+ } finally {
+ if (origBus != bus) {
+ BusFactory.setThreadDefaultBus(origBus);
+ }
+ if (origLoader != null) {
+ origLoader.reset();
+ }
+ }
+ }
+ private void addToChain(InterceptorChain chain, Message m) {
+ Collection<InterceptorProvider> providers
+ = CastUtils.cast((Collection<?>)m.get(Message.INTERCEPTOR_PROVIDERS));
+ if (providers != null) {
+ for (InterceptorProvider p : providers) {
+ chain.add(p.getInInterceptors());
+ }
+ }
+ Collection<Interceptor<? extends Message>> is
+ = CastUtils.cast((Collection<?>)m.get(Message.IN_INTERCEPTORS));
+ if (is != null) {
+ chain.add(is);
+ }
+ if (m.getDestination() instanceof InterceptorProvider) {
+ chain.add(((InterceptorProvider)m.getDestination()).getInInterceptors());
+ }
+ }
+
+ protected Binding getBinding() {
+ return endpoint.getBinding();
+ }
+
+ protected void setExchangeProperties(Exchange exchange, Message m) {
+ exchange.put(Endpoint.class, endpoint);
+ exchange.put(Binding.class, getBinding());
+ exchange.put(Bus.class, bus);
+ if (exchange.getDestination() == null) {
+ exchange.setDestination(m.getDestination());
+ }
+ if (endpoint != null && endpoint.getService() != null) {
+ exchange.put(Service.class, endpoint.getService());
+
+ EndpointInfo endpointInfo = endpoint.getEndpointInfo();
+
+ if (endpointInfo.getService() != null) {
+ QName serviceQName = endpointInfo.getService().getName();
+ exchange.put(Message.WSDL_SERVICE, serviceQName);
+
+ QName interfaceQName = endpointInfo.getService().getInterface().getName();
+ exchange.put(Message.WSDL_INTERFACE, interfaceQName);
+
+
+ QName portQName = endpointInfo.getName();
+ exchange.put(Message.WSDL_PORT, portQName);
+ URI wsdlDescription = endpointInfo.getProperty("URI", URI.class);
+ if (wsdlDescription == null && !endpointInfo.hasProperty("URI")) {
+ String address = endpointInfo.getAddress();
+ try {
+ wsdlDescription = new URI(address + "?wsdl");
+ } catch (URISyntaxException e) {
+ // do nothing
+ }
+ endpointInfo.setProperty("URI", wsdlDescription);
+ }
+ exchange.put(Message.WSDL_DESCRIPTION, wsdlDescription);
+ }
+ } else {
+ exchange.put(Service.class, null);
+ }
+ }
+
+ public Endpoint getEndpoint() {
+ return endpoint;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/validation/AbstractBeanValidationInterceptor.java b/transform/src/patch/java/org/apache/cxf/validation/AbstractBeanValidationInterceptor.java
new file mode 100644
index 0000000..3fecf1a
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/validation/AbstractBeanValidationInterceptor.java
@@ -0,0 +1,65 @@
+/**
+ * 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.validation;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+import javax.validation.ValidationException;
+
+import org.apache.cxf.message.Message;
+
+public abstract class AbstractBeanValidationInterceptor extends AbstractValidationInterceptor {
+ protected AbstractBeanValidationInterceptor(String phase) {
+ super(phase);
+ }
+
+ @Override
+ protected Object getServiceObject(Message message) {
+ return checkNotNull(super.getServiceObject(message), "SERVICE_OBJECT_NULL");
+ }
+
+ @Override
+ protected Method getServiceMethod(Message message) {
+ return (Method)checkNotNull(super.getServiceMethod(message), "SERVICE_METHOD_NULL");
+ }
+
+ private Object checkNotNull(Object object, String name) {
+ if (object == null) {
+ String message = new org.apache.cxf.common.i18n.Message(name, BUNDLE).toString();
+ LOG.severe(message);
+ throw new ValidationException(message);
+ }
+ return object;
+ }
+
+ @Override
+ protected void handleValidation(final Message message, final Object resourceInstance,
+ final Method method, final List<Object> arguments) {
+ if (!arguments.isEmpty()) {
+ BeanValidationProvider provider = getProvider(message);
+ provider.validateParameters(resourceInstance, method, unwrapArgs(arguments).toArray());
+ message.getExchange().put(BeanValidationProvider.class, provider);
+ }
+ }
+
+ protected List<Object> unwrapArgs(List<Object> arguments) {
+ return arguments;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/workqueue/AutomaticWorkQueueImpl.java b/transform/src/patch/java/org/apache/cxf/workqueue/AutomaticWorkQueueImpl.java
new file mode 100644
index 0000000..bff113e
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/workqueue/AutomaticWorkQueueImpl.java
@@ -0,0 +1,619 @@
+/**
+ * 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.workqueue;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.concurrent.DelayQueue;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.classloader.ClassLoaderUtils.ClassLoaderHolder;
+import org.apache.cxf.common.injection.NoJSR250Annotations;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.ReflectionUtil;
+
+@NoJSR250Annotations
+public class AutomaticWorkQueueImpl implements AutomaticWorkQueue {
+ public static final String PROPERTY_NAME = "name";
+ static final int DEFAULT_MAX_QUEUE_SIZE = 256;
+ private static final Logger LOG =
+ LogUtils.getL7dLogger(AutomaticWorkQueueImpl.class);
+
+
+ String name = "default";
+ int maxQueueSize;
+ int initialThreads;
+ int lowWaterMark;
+ int highWaterMark;
+ long dequeueTimeout;
+ AtomicInteger approxThreadCount = new AtomicInteger();
+
+ ThreadPoolExecutor executor;
+ Method addWorkerMethod;
+ Object[] addWorkerArgs;
+
+ AWQThreadFactory threadFactory;
+ ReentrantLock mainLock;
+ final ReentrantLock addThreadLock = new ReentrantLock();
+
+ DelayQueue<DelayedTaskWrapper> delayQueue;
+ WatchDog watchDog;
+
+ boolean shared;
+ int sharedCount;
+
+ private List<PropertyChangeListener> changeListenerList;
+
+ public AutomaticWorkQueueImpl() {
+ this(DEFAULT_MAX_QUEUE_SIZE);
+ }
+ public AutomaticWorkQueueImpl(String name) {
+ this(DEFAULT_MAX_QUEUE_SIZE, name);
+ }
+ public AutomaticWorkQueueImpl(int max) {
+ this(max, "default");
+ }
+ public AutomaticWorkQueueImpl(int max, String name) {
+ this(max,
+ 0,
+ 25,
+ 5,
+ 2 * 60 * 1000L,
+ name);
+ }
+ public AutomaticWorkQueueImpl(int mqs,
+ int initialThreads,
+ int highWaterMark,
+ int lowWaterMark,
+ long dequeueTimeout) {
+ this(mqs, initialThreads, highWaterMark, lowWaterMark, dequeueTimeout, "default");
+ }
+ public AutomaticWorkQueueImpl(int mqs,
+ int initialThreads,
+ int highWaterMark,
+ int lowWaterMark,
+ long dequeueTimeout,
+ String name) {
+ this.maxQueueSize = mqs == -1 ? DEFAULT_MAX_QUEUE_SIZE : mqs;
+ this.initialThreads = initialThreads;
+ this.highWaterMark = -1 == highWaterMark ? Integer.MAX_VALUE : highWaterMark;
+ this.lowWaterMark = -1 == lowWaterMark ? Integer.MAX_VALUE : lowWaterMark;
+ this.dequeueTimeout = dequeueTimeout;
+ this.name = name;
+ this.changeListenerList = new ArrayList<>();
+ }
+
+ public void addChangeListener(PropertyChangeListener listener) {
+ this.changeListenerList.add(listener);
+ }
+
+ public void removeChangeListener(PropertyChangeListener listener) {
+ this.changeListenerList.remove(listener);
+ }
+
+ public void notifyChangeListeners(PropertyChangeEvent event) {
+ for (PropertyChangeListener listener : changeListenerList) {
+ listener.propertyChange(event);
+ }
+ }
+
+ public void setShared(boolean shared) {
+ this.shared = shared;
+ }
+ public boolean isShared() {
+ return shared;
+ }
+ public void addSharedUser() {
+ sharedCount++;
+ }
+ public void removeSharedUser() {
+ sharedCount--;
+ }
+ public int getShareCount() {
+ return sharedCount;
+ }
+
+ protected synchronized ThreadPoolExecutor getExecutor() {
+ if (executor == null) {
+ threadFactory = createThreadFactory(name);
+ executor = new ThreadPoolExecutor(lowWaterMark,
+ highWaterMark,
+ TimeUnit.MILLISECONDS.toMillis(dequeueTimeout),
+ TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue<Runnable>(maxQueueSize),
+ threadFactory) {
+ @Override
+ protected void terminated() {
+ ThreadFactory f = executor.getThreadFactory();
+ if (f instanceof AWQThreadFactory) {
+ ((AWQThreadFactory)f).shutdown();
+ }
+ if (watchDog != null) {
+ watchDog.shutdown();
+ }
+ }
+ };
+
+
+ if (LOG.isLoggable(Level.FINE)) {
+ StringBuilder buf = new StringBuilder(128).append("Constructing automatic work queue with:\n")
+ .append("max queue size: ").append(maxQueueSize).append('\n')
+ .append("initialThreads: ").append(initialThreads).append('\n')
+ .append("lowWaterMark: ").append(lowWaterMark).append('\n')
+ .append("highWaterMark: ").append(highWaterMark).append('\n');
+ LOG.fine(buf.toString());
+ }
+
+ if (initialThreads > highWaterMark) {
+ initialThreads = highWaterMark;
+ }
+
+ // as we cannot prestart more core than corePoolSize initial threads, we temporarily
+ // change the corePoolSize to the number of initial threads
+ // this is important as otherwise these threads will be created only when
+ // the queue has filled up,
+ // potentially causing problems with starting up under heavy load
+ if (initialThreads < Integer.MAX_VALUE && initialThreads > 0) {
+ executor.setCorePoolSize(initialThreads);
+ int started = executor.prestartAllCoreThreads();
+ if (started < initialThreads) {
+ LOG.log(Level.WARNING, "THREAD_START_FAILURE_MSG",
+ new Object[] {started, initialThreads});
+ }
+ executor.setCorePoolSize(lowWaterMark);
+ }
+
+ ReentrantLock l;
+ try {
+ Field f = ThreadPoolExecutor.class.getDeclaredField("mainLock");
+ ReflectionUtil.setAccessible(f);
+ l = (ReentrantLock)f.get(executor);
+ } catch (Throwable t) {
+ l = new ReentrantLock();
+ }
+ mainLock = l;
+
+
+ try {
+ //java 7
+ addWorkerMethod = ThreadPoolExecutor.class.getDeclaredMethod("addWorker",
+ Runnable.class, Boolean.TYPE);
+ addWorkerArgs = new Object[] {null, Boolean.FALSE};
+ } catch (Throwable t2) {
+ //nothing we cando
+ }
+ }
+ return executor;
+ }
+ private AWQThreadFactory createThreadFactory(final String nm) {
+ ThreadGroup group;
+ try {
+ //Try and find the highest level ThreadGroup that we're allowed to use.
+ //That SHOULD allow the default classloader and thread locals and such
+ //to be the least likely to cause issues down the road.
+ group = AccessController.doPrivileged(
+ new PrivilegedAction<ThreadGroup>() {
+ public ThreadGroup run() {
+ ThreadGroup group = Thread.currentThread().getThreadGroup();
+ ThreadGroup parent = group;
+ try {
+ while (parent != null) {
+ group = parent;
+ parent = parent.getParent();
+ }
+ } catch (SecurityException se) {
+ //ignore - if we get here, the "group" is as high as
+ //the security manager will allow us to go. Use that one.
+ }
+ return new ThreadGroup(group, nm + "-workqueue");
+ }
+ }
+ );
+ } catch (SecurityException e) {
+ group = new ThreadGroup(nm + "-workqueue");
+ }
+ return new AWQThreadFactory(group, nm);
+ }
+
+ static class DelayedTaskWrapper implements Delayed, Runnable {
+ long trigger;
+ Runnable work;
+
+ DelayedTaskWrapper(Runnable work, long delay) {
+ this.work = work;
+ trigger = System.currentTimeMillis() + delay;
+ }
+
+ public long getDelay(TimeUnit unit) {
+ long n = trigger - System.currentTimeMillis();
+ return unit.convert(n, TimeUnit.MILLISECONDS);
+ }
+
+ public int compareTo(Delayed delayed) {
+ long other = ((DelayedTaskWrapper)delayed).trigger;
+ int returnValue;
+ if (this.trigger < other) {
+ returnValue = -1;
+ } else if (this.trigger > other) {
+ returnValue = 1;
+ } else {
+ returnValue = 0;
+ }
+ return returnValue;
+ }
+
+ public void run() {
+ work.run();
+ }
+
+ }
+
+ class WatchDog extends Thread {
+ DelayQueue<DelayedTaskWrapper> delayQueue;
+ AtomicBoolean shutdown = new AtomicBoolean(false);
+
+ WatchDog(DelayQueue<DelayedTaskWrapper> queue) {
+ delayQueue = queue;
+ }
+
+ public void shutdown() {
+ shutdown.set(true);
+ // to exit the waiting thread
+ interrupt();
+ }
+
+ public void run() {
+ DelayedTaskWrapper task;
+ try {
+ while (!shutdown.get()) {
+ task = delayQueue.take();
+ if (task != null) {
+ try {
+ execute(task);
+ } catch (Exception ex) {
+ LOG.warning("Executing the task from DelayQueue with exception: " + ex);
+ }
+ }
+ }
+ } catch (InterruptedException e) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.finer("The DelayQueue watchdog Task is stopping");
+ }
+ }
+
+ }
+
+ }
+ class AWQThreadFactory implements ThreadFactory {
+ final AtomicInteger threadNumber = new AtomicInteger(1);
+ ThreadGroup group;
+ String name;
+ ClassLoader loader;
+
+ AWQThreadFactory(ThreadGroup gp, String nm) {
+ group = gp;
+ name = nm;
+ //force the loader to be the loader of CXF, not the application loader
+ loader = AutomaticWorkQueueImpl.class.getClassLoader();
+ }
+
+ public Thread newThread(final Runnable r) {
+ if (group.isDestroyed()) {
+ group = new ThreadGroup(group.getParent(), name + "-workqueue");
+ }
+ Runnable wrapped = new Runnable() {
+ public void run() {
+ approxThreadCount.incrementAndGet();
+ try {
+ r.run();
+ } finally {
+ approxThreadCount.decrementAndGet();
+ }
+ }
+ };
+ final Thread t = new Thread(group,
+ wrapped,
+ name + "-workqueue-" + threadNumber.getAndIncrement(),
+ 0);
+ AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+ public Boolean run() {
+ t.setContextClassLoader(loader);
+ return true;
+ }
+ });
+ t.setDaemon(true);
+ if (t.getPriority() != Thread.NORM_PRIORITY) {
+ t.setPriority(Thread.NORM_PRIORITY);
+ }
+ return t;
+ }
+ public void setName(String s) {
+ name = s;
+ }
+ public void shutdown() {
+ if (!group.isDestroyed()) {
+ try {
+ group.destroy();
+ group.setDaemon(true);
+ } catch (Throwable t) {
+ //ignore
+ }
+ }
+ }
+ }
+
+ public void setName(String s) {
+ name = s;
+ if (threadFactory != null) {
+ threadFactory.setName(s);
+ }
+ }
+ public String getName() {
+ return name;
+ }
+
+ public String toString() {
+ return new StringBuilder(super.toString())
+ .append(" [queue size: ").append(getSize())
+ .append(", max size: ").append(maxQueueSize)
+ .append(", threads: ").append(getPoolSize())
+ .append(", active threads: ").append(getActiveCount())
+ .append(", low water mark: ").append(getLowWaterMark())
+ .append(", high water mark: ").append(getHighWaterMark())
+ .append(']').toString();
+ }
+
+ public void execute(final Runnable command) {
+ //Grab the context classloader of this thread. We'll make sure we use that
+ //on the thread the runnable actually runs on.
+
+ final ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ Runnable r = new Runnable() {
+ public void run() {
+ ClassLoaderHolder orig = ClassLoaderUtils.setThreadContextClassloader(loader);
+ try {
+ command.run();
+ } finally {
+ if (orig != null) {
+ orig.reset();
+ }
+ }
+ }
+ };
+ //The ThreadPoolExecutor in the JDK doesn't expand the number
+ //of threads until the queue is full. However, we would
+ //prefer the number of threads to expand immediately and
+ //only uses the queue if we've reached the maximum number
+ //of threads.
+ ThreadPoolExecutor ex = getExecutor();
+ ex.execute(r);
+ if (addWorkerMethod != null
+ && !ex.getQueue().isEmpty()
+ && this.approxThreadCount.get() < highWaterMark
+ && addThreadLock.tryLock()) {
+ try {
+ mainLock.lock();
+ try {
+ int ps = this.getPoolSize();
+ int sz = executor.getQueue().size();
+ int sz2 = this.getActiveCount();
+
+ if ((sz + sz2) > ps) {
+ // Needs --add-opens java.base/java.util.concurrent=ALL-UNNAMED for JDK16+
+ ReflectionUtil.setAccessible(addWorkerMethod).invoke(executor, addWorkerArgs);
+ }
+ } catch (Exception exc) {
+ //ignore
+ } finally {
+ mainLock.unlock();
+ }
+ } finally {
+ addThreadLock.unlock();
+ }
+ }
+ }
+
+ // WorkQueue interface
+ public void execute(Runnable work, long timeout) {
+ try {
+ execute(work);
+ } catch (RejectedExecutionException ree) {
+ try {
+ if (!getExecutor().getQueue().offer(work, timeout, TimeUnit.MILLISECONDS)) {
+ throw ree;
+ }
+ } catch (InterruptedException ie) {
+ throw ree;
+ }
+ }
+ }
+
+ public synchronized void schedule(final Runnable work, final long delay) {
+ if (delayQueue == null) {
+ delayQueue = new DelayQueue<>();
+ watchDog = new WatchDog(delayQueue);
+ watchDog.setDaemon(true);
+ watchDog.start();
+ }
+ delayQueue.put(new DelayedTaskWrapper(work, delay));
+ }
+
+ // AutomaticWorkQueue interface
+
+ public void shutdown(boolean processRemainingWorkItems) {
+ if (executor != null) {
+ if (!processRemainingWorkItems) {
+ executor.getQueue().clear();
+ }
+ executor.shutdown();
+ }
+ }
+
+
+ /**
+ * Gets the maximum size (capacity) of the backing queue.
+ * @return the maximum size (capacity) of the backing queue.
+ */
+ public long getMaxSize() {
+ return maxQueueSize;
+ }
+
+ /**
+ * Gets the current size of the backing queue.
+ * @return the current size of the backing queue.
+ */
+ public long getSize() {
+ return executor == null ? 0 : executor.getQueue().size();
+ }
+
+
+ public boolean isEmpty() {
+ return executor == null || executor.getQueue().isEmpty();
+ }
+
+ public boolean isFull() {
+ return executor != null && executor.getQueue().remainingCapacity() == 0;
+ }
+
+ public int getHighWaterMark() {
+ int hwm = executor == null ? highWaterMark : executor.getMaximumPoolSize();
+ return hwm == Integer.MAX_VALUE ? -1 : hwm;
+ }
+
+ public int getLowWaterMark() {
+ int lwm = executor == null ? lowWaterMark : executor.getCorePoolSize();
+ return lwm == Integer.MAX_VALUE ? -1 : lwm;
+ }
+
+ public int getInitialSize() {
+ return this.initialThreads;
+ }
+
+ public void setHighWaterMark(int hwm) {
+ highWaterMark = hwm < 0 ? Integer.MAX_VALUE : hwm;
+ if (executor != null) {
+ notifyChangeListeners(new PropertyChangeEvent(this, "highWaterMark",
+ this.executor.getMaximumPoolSize(), hwm));
+ executor.setMaximumPoolSize(highWaterMark);
+ }
+ }
+
+ public void setLowWaterMark(int lwm) {
+ lowWaterMark = lwm < 0 ? 0 : lwm;
+ if (executor != null) {
+ notifyChangeListeners(new PropertyChangeEvent(this, "lowWaterMark",
+ this.executor.getCorePoolSize(), lwm));
+ executor.setCorePoolSize(lowWaterMark);
+ }
+ }
+
+ public void setInitialSize(int initialSize) {
+ notifyChangeListeners(new PropertyChangeEvent(this, "initialSize", this.initialThreads, initialSize));
+ this.initialThreads = initialSize;
+ }
+
+ public void setQueueSize(int size) {
+ notifyChangeListeners(new PropertyChangeEvent(this, "queueSize", this.maxQueueSize, size));
+ this.maxQueueSize = size;
+ }
+
+ public void setDequeueTimeout(long l) {
+ notifyChangeListeners(new PropertyChangeEvent(this, "dequeueTimeout", this.dequeueTimeout, l));
+ this.dequeueTimeout = l;
+ }
+
+ public boolean isShutdown() {
+ if (executor == null) {
+ return false;
+ }
+ return executor.isShutdown();
+ }
+ public int getLargestPoolSize() {
+ if (executor == null) {
+ return 0;
+ }
+ return executor.getLargestPoolSize();
+ }
+ public int getPoolSize() {
+ if (executor == null) {
+ return 0;
+ }
+ return executor.getPoolSize();
+ }
+ public int getActiveCount() {
+ if (executor == null) {
+ return 0;
+ }
+ return executor.getActiveCount();
+ }
+ public void update(Dictionary<String, String> config) {
+ String s = config.get("highWaterMark");
+ if (s != null) {
+ this.highWaterMark = Integer.parseInt(s);
+ }
+ s = config.get("lowWaterMark");
+ if (s != null) {
+ this.lowWaterMark = Integer.parseInt(s);
+ }
+ s = config.get("initialSize");
+ if (s != null) {
+ this.initialThreads = Integer.parseInt(s);
+ }
+ s = config.get("dequeueTimeout");
+ if (s != null) {
+ this.dequeueTimeout = Long.parseLong(s);
+ }
+ s = config.get("queueSize");
+ if (s != null) {
+ this.maxQueueSize = Integer.parseInt(s);
+ }
+ }
+ public Dictionary<String, String> getProperties() {
+ Dictionary<String, String> properties = new Hashtable<>();
+ NumberFormat nf = NumberFormat.getIntegerInstance();
+ properties.put("name", nf.format(getName()));
+ properties.put("highWaterMark", nf.format(getHighWaterMark()));
+ properties.put("lowWaterMark", nf.format(getLowWaterMark()));
+ properties.put("initialSize", nf.format(getLowWaterMark()));
+ properties.put("dequeueTimeout", nf.format(getLowWaterMark()));
+ properties.put("queueSize", nf.format(getLowWaterMark()));
+ return properties;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/ws/addressing/MAPAggregator.java b/transform/src/patch/java/org/apache/cxf/ws/addressing/MAPAggregator.java
new file mode 100644
index 0000000..206494d
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/ws/addressing/MAPAggregator.java
@@ -0,0 +1,224 @@
+/**
+ * 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.addressing;
+
+import java.util.Collection;
+
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.AbstractPhaseInterceptor;
+import org.apache.cxf.phase.Phase;
+import org.apache.cxf.phase.PhaseInterceptor;
+
+/**
+ * Logical Handler responsible for aggregating the Message Addressing
+ * Properties for outgoing messages.
+ */
+public class MAPAggregator extends AbstractPhaseInterceptor<Message> {
+ public static final String USING_ADDRESSING = MAPAggregator.class.getName() + ".usingAddressing";
+ public static final String ADDRESSING_DISABLED = MAPAggregator.class.getName() + ".addressingDisabled";
+ public static final String DECOUPLED_DESTINATION = MAPAggregator.class.getName()
+ + ".decoupledDestination";
+ public static final String ACTION_VERIFIED = MAPAggregator.class.getName() + ".actionVerified";
+ public static final String ADDRESSING_NAMESPACE = MAPAggregator.class.getName() + ".addressingNamespace";
+
+ public interface MAPAggregatorLoader {
+ MAPAggregator createImplementation(MAPAggregator mag);
+ }
+
+ protected MessageIdCache messageIdCache;
+ protected boolean usingAddressingAdvisory = true;
+ protected boolean addressingRequired;
+ protected boolean allowDuplicates = true;
+ protected WSAddressingFeature.AddressingResponses addressingResponses
+ = WSAddressingFeature.AddressingResponses.ALL;
+
+ /**
+ * The real implementation of the MAPAggregator interceptor
+ */
+ private MAPAggregator impl;
+
+ /**
+ * Constructor.
+ */
+ public MAPAggregator() {
+ super(MAPAggregator.class.getName(), Phase.PRE_LOGICAL);
+ addBefore("org.apache.cxf.interceptor.OneWayProcessorInterceptor");
+ }
+
+ /**
+ * Indicates if duplicate messageIDs are allowed.
+ * @return true if duplicate messageIDs are allowed
+ */
+ public boolean allowDuplicates() {
+ if (impl != null) {
+ return impl.allowDuplicates();
+ }
+ return allowDuplicates;
+ }
+
+ /**
+ * Allows/disallows duplicate messageIdDs.
+ * @param ad whether duplicate messageIDs are allowed
+ */
+ public void setAllowDuplicates(boolean ad) {
+ if (impl != null) {
+ impl.setAllowDuplicates(ad);
+ }
+ allowDuplicates = ad;
+ }
+
+ /**
+ * Whether the presence of the <wsaw:UsingAddressing> element
+ * in the WSDL is purely advisory, i.e. its absence doesn't prevent
+ * the encoding of WS-A headers.
+ *
+ * @return true if the presence of the <wsaw:UsingAddressing> element is
+ * advisory
+ */
+ public boolean isUsingAddressingAdvisory() {
+ if (impl != null) {
+ return impl.isUsingAddressingAdvisory();
+ }
+ return usingAddressingAdvisory;
+ }
+
+ /**
+ * Controls whether the presence of the <wsaw:UsingAddressing> element
+ * in the WSDL is purely advisory, i.e. its absence doesn't prevent
+ * the encoding of WS-A headers.
+ *
+ * @param advisory true if the presence of the <wsaw:UsingAddressing>
+ * element is to be advisory
+ */
+ public void setUsingAddressingAdvisory(boolean advisory) {
+ if (impl != null) {
+ impl.setUsingAddressingAdvisory(advisory);
+ }
+ usingAddressingAdvisory = advisory;
+ }
+
+ /**
+ * Whether the use of addressing is completely required for this endpoint
+ *
+ * @return true if addressing is required
+ */
+ public boolean isAddressingRequired() {
+ if (impl != null) {
+ return impl.addressingRequired;
+ }
+ return addressingRequired;
+ }
+ /**
+ * Sets whether the use of addressing is completely required for this endpoint
+ *
+ */
+ public void setAddressingRequired(boolean required) {
+ if (impl != null) {
+ impl.setAddressingRequired(required);
+ }
+ addressingRequired = required;
+ }
+
+ /**
+ * Sets Addresing Response
+ *
+ */
+ public void setAddressingResponses(WSAddressingFeature.AddressingResponses responses) {
+ if (impl != null) {
+ impl.setAddressingResponses(responses);
+ }
+ addressingResponses = responses;
+ }
+
+ /**
+ * Returns the cache used to enforce duplicate message IDs when
+ * {@link #allowDuplicates()} returns {@code false}.
+ *
+ * @return the cache used to enforce duplicate message IDs
+ */
+ public MessageIdCache getMessageIdCache() {
+ if (impl != null) {
+ return impl.getMessageIdCache();
+ }
+ return messageIdCache;
+ }
+
+ /**
+ * Sets the cache used to enforce duplicate message IDs when
+ * {@link #allowDuplicates()} returns {@code false}.
+ *
+ * @param messageIdCache the cache to use
+ *
+ * @throws NullPointerException if {@code messageIdCache} is {@code null}
+ */
+ public void setMessageIdCache(MessageIdCache messageIdCache) {
+ if (messageIdCache == null) {
+ throw new NullPointerException("messageIdCache cannot be null.");
+ }
+ if (impl != null) {
+ impl.setMessageIdCache(messageIdCache);
+ }
+ this.messageIdCache = messageIdCache;
+ }
+
+ /**
+ * Sets Addressing Response
+ *
+ */
+ public WSAddressingFeature.AddressingResponses getAddressingResponses() {
+ if (impl != null) {
+ return impl.getAddressingResponses();
+ }
+ return addressingResponses;
+ }
+
+ /**
+ * Invoked for normal processing of inbound and outbound messages.
+ *
+ * @param message the current message
+ */
+ public void handleMessage(Message message) {
+ if (impl == null) {
+ //load impl
+ MAPAggregatorLoader loader = message.getExchange().getBus()
+ .getExtension(MAPAggregatorLoader.class);
+ impl = loader.createImplementation(this);
+ }
+ impl.handleMessage(message);
+ }
+
+ @Override
+ public void handleFault(Message message) {
+ if (impl != null) {
+ impl.handleFault(message);
+ }
+ }
+
+
+ @Override
+ public Collection<PhaseInterceptor<? extends Message>> getAdditionalInterceptors() {
+ if (impl != null) {
+ return impl.getAdditionalInterceptors();
+ }
+ return super.getAdditionalInterceptors();
+ }
+
+
+}