You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2020/07/01 14:41:26 UTC

[juneau] branch master updated: Add header support to @Remote annotation.

This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 1dff96f  Add header support to @Remote annotation.
1dff96f is described below

commit 1dff96f7fd454e2c77fd9278ec617779bdb13454
Author: JamesBognar <ja...@salesforce.com>
AuthorDate: Wed Jul 1 10:41:20 2020 -0400

    Add header support to @Remote annotation.
---
 .../org/apache/juneau/http/BasicHeaderTest.java}   |  89 ++++++++--------
 .../juneau/http/BasicNameValuePairTest.java}       |  88 ++++++++--------
 .../org/apache/juneau/http/HeaderSupplierTest.java |  51 +++++++++
 .../org/apache/juneau/http/BasicNameValuePair.java |  15 +++
 .../{remote/Remote.java => HeaderSupplier.java}    | 115 +++++++++++++--------
 .../Remote.java => NameValuePairSupplier.java}     | 115 +++++++++++++--------
 .../org/apache/juneau/http/header/BasicHeader.java |  15 +++
 .../java/org/apache/juneau/http/remote/Remote.java |  63 +++++++++++
 .../apache/juneau/internal/CollectionUtils.java    |  36 +++++++
 juneau-doc/docs/ReleaseNotes/8.1.4.html            |   8 +-
 .../juneau/rest/client/remote/RemoteMeta.java      |  27 ++++-
 .../org/apache/juneau/rest/client2/RestClient.java |  84 +++++++--------
 12 files changed, 480 insertions(+), 226 deletions(-)

diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/Remote.java b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/http/BasicHeaderTest.java
similarity index 64%
copy from juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/Remote.java
copy to juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/http/BasicHeaderTest.java
index c2e5217..78f54d3 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/Remote.java
+++ b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/http/BasicHeaderTest.java
@@ -1,45 +1,44 @@
-// ***************************************************************************************************************************
-// * 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.juneau.http.remote;
-
-import static java.lang.annotation.ElementType.*;
-import static java.lang.annotation.RetentionPolicy.*;
-
-import java.lang.annotation.*;
-
-/**
- * Identifies a proxy against a REST interface.
- *
- * <ul class='seealso'>
- * 	<li class='link'>{@doc juneau-rest-client.RestProxies}
- * </ul>
- */
-@Documented
-@Target({TYPE})
-@Retention(RUNTIME)
-@Inherited
-public @interface Remote {
-
-	/**
-	 * REST service path.
-	 *
-	 * <p>
-	 * The possible values are:
-	 * <ul class='spaced-list'>
-	 * 	<li>An absolute URL.
-	 * 	<li>A relative URL interpreted as relative to the root URL defined on the <c>RestClient</c>
-	 * 	<li>No path interpreted as the class name (e.g. <js>"http://localhost/root-url/org.foo.MyInterface"</js>)
-	 * </ul>
-	 */
-	String path() default "";
-}
+// ***************************************************************************************************************************
+// * 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.juneau.http;
+
+import static org.junit.Assert.*;
+import static org.junit.runners.MethodSorters.*;
+
+import org.apache.juneau.http.header.*;
+import org.junit.*;
+
+@FixMethodOrder(NAME_ASCENDING)
+public class BasicHeaderTest {
+
+	@Test
+	public void a01_of_pairString() {
+		BasicHeader h = BasicHeader.ofPair("Foo:bar");
+		assertEquals("Foo", h.getName());
+		assertEquals("bar", h.getValue());
+
+		h = BasicHeader.ofPair(" Foo : bar ");
+		assertEquals("Foo", h.getName());
+		assertEquals("bar", h.getValue());
+
+		h = BasicHeader.ofPair(" Foo : bar : baz ");
+		assertEquals("Foo", h.getName());
+		assertEquals("bar : baz", h.getValue());
+
+		h = BasicHeader.ofPair("Foo");
+		assertEquals("Foo", h.getName());
+		assertEquals("", h.getValue());
+
+		assertNull(BasicHeader.ofPair((String)null));
+	}
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/Remote.java b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/http/BasicNameValuePairTest.java
similarity index 64%
copy from juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/Remote.java
copy to juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/http/BasicNameValuePairTest.java
index c2e5217..e8d6b73 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/Remote.java
+++ b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/http/BasicNameValuePairTest.java
@@ -1,45 +1,43 @@
-// ***************************************************************************************************************************
-// * 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.juneau.http.remote;
-
-import static java.lang.annotation.ElementType.*;
-import static java.lang.annotation.RetentionPolicy.*;
-
-import java.lang.annotation.*;
-
-/**
- * Identifies a proxy against a REST interface.
- *
- * <ul class='seealso'>
- * 	<li class='link'>{@doc juneau-rest-client.RestProxies}
- * </ul>
- */
-@Documented
-@Target({TYPE})
-@Retention(RUNTIME)
-@Inherited
-public @interface Remote {
-
-	/**
-	 * REST service path.
-	 *
-	 * <p>
-	 * The possible values are:
-	 * <ul class='spaced-list'>
-	 * 	<li>An absolute URL.
-	 * 	<li>A relative URL interpreted as relative to the root URL defined on the <c>RestClient</c>
-	 * 	<li>No path interpreted as the class name (e.g. <js>"http://localhost/root-url/org.foo.MyInterface"</js>)
-	 * </ul>
-	 */
-	String path() default "";
-}
+// ***************************************************************************************************************************
+// * 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.juneau.http;
+
+import static org.junit.Assert.*;
+import static org.junit.runners.MethodSorters.*;
+
+import org.junit.*;
+
+@FixMethodOrder(NAME_ASCENDING)
+public class BasicNameValuePairTest {
+
+	@Test
+	public void a01_of_pairString() {
+		BasicNameValuePair h = BasicNameValuePair.ofPair("Foo:bar");
+		assertEquals("Foo", h.getName());
+		assertEquals("bar", h.getValue());
+
+		h = BasicNameValuePair.ofPair(" Foo : bar ");
+		assertEquals("Foo", h.getName());
+		assertEquals("bar", h.getValue());
+
+		h = BasicNameValuePair.ofPair(" Foo : bar : baz ");
+		assertEquals("Foo", h.getName());
+		assertEquals("bar : baz", h.getValue());
+
+		h = BasicNameValuePair.ofPair("Foo");
+		assertEquals("Foo", h.getName());
+		assertEquals("", h.getValue());
+
+		assertNull(BasicNameValuePair.ofPair((String)null));
+	}
+}
diff --git a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/http/HeaderSupplierTest.java b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/http/HeaderSupplierTest.java
new file mode 100644
index 0000000..4900fc9
--- /dev/null
+++ b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/http/HeaderSupplierTest.java
@@ -0,0 +1,51 @@
+// ***************************************************************************************************************************
+// * 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.juneau.http;
+
+import static org.apache.juneau.assertions.Assertions.*;
+import static org.junit.runners.MethodSorters.*;
+
+import org.apache.http.*;
+import org.apache.juneau.http.header.*;
+import org.junit.*;
+
+@FixMethodOrder(NAME_ASCENDING)
+public class HeaderSupplierTest {
+
+	@Test
+	public void a01_basic() {
+		HeaderSupplier h = HeaderSupplier.create();
+		assertObject(h.iterator()).json().is("[]");
+		h.add(header("Foo","bar"));
+		assertObject(h.iterator()).json().is("['Foo: bar']");
+		h.add(header("Foo","baz"));
+		assertObject(h.iterator()).json().is("['Foo: bar','Foo: baz']");
+		h.add(HeaderSupplier.create());
+		assertObject(h.iterator()).json().is("['Foo: bar','Foo: baz']");
+		h.add(HeaderSupplier.create().add(header("Foo","qux")));
+		assertObject(h.iterator()).json().is("['Foo: bar','Foo: baz','Foo: qux']");
+		h.add(HeaderSupplier.create().add(header("Foo","quux")).add(header("Foo","quuux")));
+		assertObject(h.iterator()).json().is("['Foo: bar','Foo: baz','Foo: qux','Foo: quux','Foo: quuux']");
+		h.add(HeaderSupplier.create().add(HeaderSupplier.create().add(header("Foo","ruux")).add(header("Foo","ruuux"))));
+		assertObject(h.iterator()).json().is("['Foo: bar','Foo: baz','Foo: qux','Foo: quux','Foo: quuux','Foo: ruux','Foo: ruuux']");
+		h.add((Header)null);
+		assertObject(h.iterator()).json().is("['Foo: bar','Foo: baz','Foo: qux','Foo: quux','Foo: quuux','Foo: ruux','Foo: ruuux']");
+		h.add((HeaderSupplier)null);
+		assertObject(h.iterator()).json().is("['Foo: bar','Foo: baz','Foo: qux','Foo: quux','Foo: quuux','Foo: ruux','Foo: ruuux']");
+	}
+
+	private static Header header(String name, Object val) {
+		return BasicHeader.of(name, val);
+	}
+
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/BasicNameValuePair.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/BasicNameValuePair.java
index be5601b..5ebdf31 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/BasicNameValuePair.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/BasicNameValuePair.java
@@ -45,6 +45,21 @@ public class BasicNameValuePair implements NameValuePair, Headerable {
 	}
 
 	/**
+	 * Creates a {@link NameValuePair} from a name/value pair string (e.g. <js>"Foo: bar"</js>)
+	 *
+	 * @param pair The pair string.
+	 * @return A new {@link NameValuePair} object.
+	 */
+	public static BasicNameValuePair ofPair(String pair) {
+		if (pair == null)
+			return null;
+		int i = pair.indexOf(':');
+		if (i == -1)
+			return of(pair, "");
+		return of(pair.substring(0,i).trim(), pair.substring(i+1).trim());
+	}
+
+	/**
 	 * Convenience creator using supplier.
 	 *
 	 * <p>
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/Remote.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/HeaderSupplier.java
similarity index 55%
copy from juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/Remote.java
copy to juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/HeaderSupplier.java
index c2e5217..01b6b34 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/Remote.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/HeaderSupplier.java
@@ -1,45 +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.juneau.http.remote;
-
-import static java.lang.annotation.ElementType.*;
-import static java.lang.annotation.RetentionPolicy.*;
-
-import java.lang.annotation.*;
-
-/**
- * Identifies a proxy against a REST interface.
- *
- * <ul class='seealso'>
- * 	<li class='link'>{@doc juneau-rest-client.RestProxies}
- * </ul>
- */
-@Documented
-@Target({TYPE})
-@Retention(RUNTIME)
-@Inherited
-public @interface Remote {
-
-	/**
-	 * REST service path.
-	 *
-	 * <p>
-	 * The possible values are:
-	 * <ul class='spaced-list'>
-	 * 	<li>An absolute URL.
-	 * 	<li>A relative URL interpreted as relative to the root URL defined on the <c>RestClient</c>
-	 * 	<li>No path interpreted as the class name (e.g. <js>"http://localhost/root-url/org.foo.MyInterface"</js>)
-	 * </ul>
-	 */
-	String path() default "";
-}
+// ***************************************************************************************************************************
+// * 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.juneau.http;
+
+import java.util.*;
+import java.util.concurrent.*;
+
+import org.apache.http.*;
+import org.apache.juneau.internal.*;
+
+/**
+ * Specifies a dynamic supplier of {@link Header} objects.
+ *
+ * This class is thread safe.
+ */
+public class HeaderSupplier implements Iterable<Header> {
+
+	/** Represents no header supplier */
+	public final class Null extends HeaderSupplier {}
+
+	/**
+	 * Convenience creator.
+	 *
+	 * @return A new {@link HeaderSupplier} object.
+	 */
+	public static HeaderSupplier create() {
+		return new HeaderSupplier();
+	}
+
+	private final List<Iterable<Header>> headers = new CopyOnWriteArrayList<>();
+
+	/**
+	 * Add a header to this supplier.
+	 *
+	 * @param h The header to add. <jk>null</jk> values are ignored.
+	 * @return This object (for method chaining).
+	 */
+	public HeaderSupplier add(Header h) {
+		if (h != null)
+			headers.add(Collections.singleton(h));
+		return this;
+	}
+
+	/**
+	 * Add a supplier to this supplier.
+	 *
+	 * @param h The supplier to add. <jk>null</jk> values are ignored.
+	 * @return This object (for method chaining).
+	 */
+	public HeaderSupplier add(HeaderSupplier h) {
+		if (h != null)
+			headers.add(h);
+		return this;
+	}
+
+	@Override
+	public Iterator<Header> iterator() {
+		return CollectionUtils.iterator(headers);
+	}
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/Remote.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/NameValuePairSupplier.java
similarity index 53%
copy from juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/Remote.java
copy to juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/NameValuePairSupplier.java
index c2e5217..4646614 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/Remote.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/NameValuePairSupplier.java
@@ -1,45 +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.juneau.http.remote;
-
-import static java.lang.annotation.ElementType.*;
-import static java.lang.annotation.RetentionPolicy.*;
-
-import java.lang.annotation.*;
-
-/**
- * Identifies a proxy against a REST interface.
- *
- * <ul class='seealso'>
- * 	<li class='link'>{@doc juneau-rest-client.RestProxies}
- * </ul>
- */
-@Documented
-@Target({TYPE})
-@Retention(RUNTIME)
-@Inherited
-public @interface Remote {
-
-	/**
-	 * REST service path.
-	 *
-	 * <p>
-	 * The possible values are:
-	 * <ul class='spaced-list'>
-	 * 	<li>An absolute URL.
-	 * 	<li>A relative URL interpreted as relative to the root URL defined on the <c>RestClient</c>
-	 * 	<li>No path interpreted as the class name (e.g. <js>"http://localhost/root-url/org.foo.MyInterface"</js>)
-	 * </ul>
-	 */
-	String path() default "";
-}
+// ***************************************************************************************************************************
+// * 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.juneau.http;
+
+import java.util.*;
+import java.util.concurrent.*;
+
+import org.apache.http.*;
+import org.apache.juneau.internal.*;
+
+/**
+ * Specifies a dynamic supplier of {@link NameValuePair} objects.
+ *
+ * This class is thread safe.
+ */
+public class NameValuePairSupplier implements Iterable<NameValuePair> {
+
+	/** Represents no header supplier */
+	public final class Null extends NameValuePairSupplier {}
+
+	/**
+	 * Convenience creator.
+	 *
+	 * @return A new {@link NameValuePairSupplier} object.
+	 */
+	public static NameValuePairSupplier create() {
+		return new NameValuePairSupplier();
+	}
+
+	private final List<Iterable<NameValuePair>> pairs = new CopyOnWriteArrayList<>();
+
+	/**
+	 * Add a name-value pair to this supplier.
+	 *
+	 * @param h The name-value pair to add. <jk>null</jk> values are ignored.
+	 * @return This object (for method chaining).
+	 */
+	public NameValuePairSupplier add(NameValuePair h) {
+		if (h != null)
+			pairs.add(Collections.singleton(h));
+		return this;
+	}
+
+	/**
+	 * Add a supplier to this supplier.
+	 *
+	 * @param h The supplier to add. <jk>null</jk> values are ignored.
+	 * @return This object (for method chaining).
+	 */
+	public NameValuePairSupplier add(NameValuePairSupplier h) {
+		if (h != null)
+			pairs.add(h);
+		return this;
+	}
+
+	@Override
+	public Iterator<NameValuePair> iterator() {
+		return CollectionUtils.iterator(pairs);
+	}
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/BasicHeader.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/BasicHeader.java
index c3e1425..4649653 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/BasicHeader.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/BasicHeader.java
@@ -53,6 +53,21 @@ public class BasicHeader implements Header, Cloneable, Serializable {
 	}
 
 	/**
+	 * Creates a {@link Header} from a name/value pair string (e.g. <js>"Foo: bar"</js>)
+	 *
+	 * @param pair The pair string.
+	 * @return A new {@link Header} object.
+	 */
+	public static BasicHeader ofPair(String pair) {
+		if (pair == null)
+			return null;
+		int i = pair.indexOf(':');
+		if (i == -1)
+			return of(pair, "");
+		return of(pair.substring(0,i).trim(), pair.substring(i+1).trim());
+	}
+
+	/**
 	 * Convenience creator.
 	 *
 	 * @param o The name value pair that makes up the header name and value.
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/Remote.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/Remote.java
index c2e5217..2df756c 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/Remote.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/Remote.java
@@ -17,6 +17,8 @@ import static java.lang.annotation.RetentionPolicy.*;
 
 import java.lang.annotation.*;
 
+import org.apache.juneau.http.*;
+
 /**
  * Identifies a proxy against a REST interface.
  *
@@ -42,4 +44,65 @@ public @interface Remote {
 	 * </ul>
 	 */
 	String path() default "";
+
+	/**
+	 * Default request headers.
+	 *
+	 * <p>
+	 * Specifies headers to set on all requests.
+	 *
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		Supports {@doc DefaultSvlVariables}
+	 * 		(e.g. <js>"$P{mySystemProperty}"</js>).
+	 * </ul>
+	 */
+	String[] headers() default {};
+
+	/**
+	 * Default request header supplier.
+	 *
+	 * <p>
+	 * Specifies a dynamic supplier of headers to set on all requests.
+	 *
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		Supplier class must provide a public no-arg constructor.
+	 * </ul>
+	 */
+	Class<? extends HeaderSupplier> headerSupplier() default HeaderSupplier.Null.class;
+
+	/**
+	 * Specifies the client version of this interface.
+	 *
+	 * <p>
+	 * Used to populate the <js>"X-Client-Version"</js> header that identifies what version of client this is
+	 * so that the server side can handle older versions accordingly.
+	 *
+	 * <p>
+	 * The format of this is a string of the format <c>#[.#[.#[...]]</c> (e.g. <js>"1.2.3"</js>).
+	 * 
+	 * <p>
+	 * The server side then uses an OSGi-version matching pattern to identify which methods to call: 
+	 * <p class='bcode w800'>
+	 * 	<jc>// Call this method if X-Client-Version is at least 2.0.
+	 * 	// Note that this also matches 2.0.1.</jc>
+	 * 	<ja>@RestMethod</ja>(name=<jsf>GET</jsf>, path=<js>"/foobar"</js>, clientVersion=<js>"2.0"</js>)
+	 * 	<jk>public</jk> Object method1()  {...}
+	 *
+	 * 	<jc>// Call this method if X-Client-Version is at least 1.1, but less than 2.0.</jc>
+	 * 	<ja>@RestMethod</ja>(name=<jsf>GET</jsf>, path=<js>"/foobar"</js>, clientVersion=<js>"[1.1,2.0)"</js>)
+	 * 	<jk>public</jk> Object method2()  {...}
+	 *
+	 * 	<jc>// Call this method if X-Client-Version is less than 1.1.</jc>
+	 * 	<ja>@RestMethod</ja>(name=<jsf>GET</jsf>, path=<js>"/foobar"</js>, clientVersion=<js>"[0,1.1)"</js>)
+	 * 	<jk>public</jk> Object method3()  {...}
+	 * </p>
+	 */
+	String version() default "";
+
+	/**
+	 * Specifies the client version header name.
+	 */
+	String versionHeader() default "X-Client-Version";
 }
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/CollectionUtils.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/CollectionUtils.java
index 81a8123..55965ad 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/CollectionUtils.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/CollectionUtils.java
@@ -71,6 +71,42 @@ public final class CollectionUtils {
 	}
 
 	/**
+	 * Creates an iterator over a list of iterable objects.
+	 *
+	 * @param <E> The element type.
+	 * @param l The iterables to iterate over.
+	 * @return A new iterator.
+	 */
+	public static <E> Iterator<E> iterator(final List<Iterable<E>> l) {
+		return new Iterator<E>() {
+			Iterator<Iterable<E>> i1 = l.iterator();
+			Iterator<E> i2 = i1.hasNext() ? i1.next().iterator() : null;
+
+			@Override /* Iterator */
+			public boolean hasNext() {
+				while (i2 != null && ! i2.hasNext())
+					i2 = (i1.hasNext() ? i1.next().iterator() : null);
+				return (i2 != null);
+			}
+
+			@Override /* Iterator */
+			public E next() {
+				hasNext();
+				if (i2 == null)
+					throw new NoSuchElementException();
+				return i2.next();
+			}
+
+			@Override /* Iterator */
+			public void remove() {
+				if (i2 == null)
+					throw new NoSuchElementException();
+				i2.remove();
+			}
+		};
+	}
+
+	/**
 	 * Adds a set of values to an existing list.
 	 *
 	 * @param appendTo
diff --git a/juneau-doc/docs/ReleaseNotes/8.1.4.html b/juneau-doc/docs/ReleaseNotes/8.1.4.html
index a114811..1a62c16 100644
--- a/juneau-doc/docs/ReleaseNotes/8.1.4.html
+++ b/juneau-doc/docs/ReleaseNotes/8.1.4.html
@@ -474,7 +474,13 @@
 	Future&lt;String&gt; f = i.doGet();
 	<jc>// Do other stuff.</jc>
 	String result = f.get();	
-		</li>p>
+		</p>
+	<li>Additions to {@link oaj.http.remote.Remote} annotation:
+	<ul>
+		<li>{@link oaj.http.remote.Remote#version version} - Adds a client version header to all requests.
+		<li>{@link oaj.http.remote.Remote#headers headers} - Adds a set of headers to all requests.
+		<li>{@link oaj.http.remote.Remote#headerSupplier headerSupplier} - Adds a dynamic supplier of headers to all requests.
+	</ul>
 </ul>
 
 <h5 class='topic w800'>juneau-rest-mock</h5>
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMeta.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMeta.java
index d9ad3b5..d555cfb 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMeta.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMeta.java
@@ -16,7 +16,10 @@ import static org.apache.juneau.internal.StringUtils.*;
 import java.lang.reflect.*;
 import java.util.*;
 
+import org.apache.http.*;
 import org.apache.juneau.collections.*;
+import org.apache.juneau.http.*;
+import org.apache.juneau.http.header.*;
 import org.apache.juneau.http.remote.*;
 import org.apache.juneau.reflect.*;
 
@@ -35,6 +38,7 @@ public class RemoteMeta {
 
 	private final Map<Method,RemoteMethodMeta> methods;
 	private final String path;
+	private final HeaderSupplier headerSupplier = HeaderSupplier.create();
 
 	/**
 	 * Constructor.
@@ -52,9 +56,21 @@ public class RemoteMeta {
 		for (org.apache.juneau.http.remote.RemoteResource r : ci.getAnnotations(org.apache.juneau.http.remote.RemoteResource.class))
 			if (! r.path().isEmpty())
 				path = trimSlashes(r.path());
-		for (Remote r : ci.getAnnotations(Remote.class))
+		for (Remote r : ci.getAnnotations(Remote.class)) {
 			if (! r.path().isEmpty())
 				path = trimSlashes(r.path());
+			for (String h : r.headers())
+				headerSupplier.add(BasicHeader.ofPair(h));
+			if (! r.version().isEmpty())
+				headerSupplier.add(ClientVersion.of(r.version()));
+			if (r.headerSupplier() != HeaderSupplier.Null.class) {
+				try {
+					headerSupplier.add(r.headerSupplier().newInstance());
+				} catch (Exception e) {
+					throw new RuntimeException("Could not instantiate HeaderSupplier class.", e);
+				}
+			}
+		}
 
 		AMap<Method,RemoteMethodMeta> methods = AMap.of();
 		for (MethodInfo m : ci.getPublicMethods())
@@ -86,4 +102,13 @@ public class RemoteMeta {
 	public String getPath() {
 		return path;
 	}
+
+	/**
+	 * Returns the headers to set on all requests.
+	 *
+	 * @return The headers to set on all requests.
+	 */
+	public Iterable<Header> getHeaders() {
+		return headerSupplier;
+	}
 }
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClient.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClient.java
index 0eac74d..ed2fc24 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClient.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClient.java
@@ -30,7 +30,6 @@ import java.util.concurrent.*;
 import java.util.function.*;
 import java.util.logging.*;
 import java.util.regex.*;
-import java.util.stream.*;
 
 import javax.net.ssl.*;
 
@@ -1872,7 +1871,8 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re
 
 	private static final Set<String> NO_BODY_METHODS = ASet.unmodifiable("GET","HEAD","DELETE","CONNECT","OPTIONS","TRACE");
 
-	private final List<Object> headers, query, formData;
+	private final HeaderSupplier headers;
+	private final NameValuePairSupplier query, formData;
 	final CloseableHttpClient httpClient;
 	private final boolean keepHttpClientOpen, leakDetection;
 	private final UrlEncodingSerializer urlEncodingSerializer;  // Used for form posts only.
@@ -1982,48 +1982,41 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re
 
 		HttpPartSerializerSession partSerializerSession = partSerializer.createPartSession(null);
 
-		Function<Object,Object> headerFunction = new Function<Object,Object>() {
-			@Override
-			public Object apply(Object x) {
-				if (x instanceof SerializedHeaderBuilder)
-					x = ((SerializedHeaderBuilder)x).serializer(partSerializerSession, false).build();
-				else if (x instanceof SerializedNameValuePairBuilder)
-					x = ((SerializedNameValuePairBuilder)x).serializer(partSerializerSession, false).build();
-				return BasicHeader.cast(x);
-			}
-		};
-
-		Function<Object,Object> nameValuePairFunction = new Function<Object,Object>() {
-			@Override
-			public Object apply(Object x) {
-				if (x instanceof SerializedNameValuePairBuilder)
-					x = ((SerializedNameValuePairBuilder)x).serializer(partSerializerSession, false).build();
-				if (x instanceof SerializedHeaderBuilder)
-					x = ((SerializedHeaderBuilder)x).serializer(partSerializerSession, false).build();
-				return BasicNameValuePair.cast(x);
-			}
-		};
-
-		this.headers = Collections.unmodifiableList(
-			getListProperty(RESTCLIENT_headers, Object.class)
-				.stream()
-				.map(headerFunction)
-				.collect(Collectors.toList())
-		);
-
-		this.query = Collections.unmodifiableList(
-			getListProperty(RESTCLIENT_query, Object.class)
-				.stream()
-				.map(nameValuePairFunction)
-				.collect(Collectors.toList())
-		);
-
-		this.formData = Collections.unmodifiableList(
-			getListProperty(RESTCLIENT_formData, Object.class)
-				.stream()
-				.map(nameValuePairFunction)
-				.collect(Collectors.toList())
-		);
+		this.headers = HeaderSupplier.create();
+		for (Object o : getListProperty(RESTCLIENT_headers, Object.class)) {
+			if (o instanceof SerializedHeaderBuilder)
+				o = ((SerializedHeaderBuilder)o).serializer(partSerializerSession, false).build();
+			else if (o instanceof SerializedNameValuePairBuilder)
+				o = ((SerializedNameValuePairBuilder)o).serializer(partSerializerSession, false).build();
+			if (o instanceof HeaderSupplier)
+				headers.add((HeaderSupplier)o);
+			else
+				headers.add(BasicHeader.cast(o));
+		}
+
+		this.query = NameValuePairSupplier.create();
+		for (Object o : getListProperty(RESTCLIENT_query, Object.class)) {
+			if (o instanceof SerializedHeaderBuilder)
+				o = ((SerializedHeaderBuilder)o).serializer(partSerializerSession, false).build();
+			else if (o instanceof SerializedNameValuePairBuilder)
+				o = ((SerializedNameValuePairBuilder)o).serializer(partSerializerSession, false).build();
+			if (o instanceof NameValuePairSupplier)
+				query.add((NameValuePairSupplier)o);
+			else
+				query.add(BasicNameValuePair.cast(o));
+		}
+
+		this.formData = NameValuePairSupplier.create();
+		for (Object o : getListProperty(RESTCLIENT_formData, Object.class)) {
+			if (o instanceof SerializedHeaderBuilder)
+				o = ((SerializedHeaderBuilder)o).serializer(partSerializerSession, false).build();
+			else if (o instanceof SerializedNameValuePairBuilder)
+				o = ((SerializedNameValuePairBuilder)o).serializer(partSerializerSession, false).build();
+			if (o instanceof NameValuePairSupplier)
+				formData.add((NameValuePairSupplier)o);
+			else
+				formData.add(BasicNameValuePair.cast(o));
+		}
 
 		this.callHandler = getInstanceProperty(RESTCLIENT_callHandler, RestCallHandler.class, BasicRestCallHandler.class, ResourceResolver.FUZZY, ps, this);
 
@@ -3039,6 +3032,9 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re
 					rc.serializer(serializer);
 					rc.parser(parser);
 
+					for (Header h : rm.getHeaders())
+						rc.header(h);
+
 					for (RemoteMethodArg a : rmm.getPathArgs())
 						rc.pathArg(a.getName(), args[a.getIndex()], a.getSchema(), a.getSerializer(s));