Streamline and refactor the azure and the azure-web adapters.

- Add SCF/Azure Gradle sample and docs.
- Move the function-azure-di-samples into standalone projects.
 - Apply the name convetion and project structure for the SCF adaptes.
   E.g.  function-sample-azure-XXX projects under the spring-cloud-function-samples root.
 - Remove the redudant samples.
 - Improve the samples docs and the Adapter generic docs.
- Streamline docs.
- Add azure web adapter sample and README.
- Add Spring Azure Functions banner for azure and azure web adapters.
- azure-web adapter fixes:
  - Fix issues in serverles-web ProxyHttpServletResponse implementation.
  - Remove the custom FunctionClassUtils utils in favor of scf-context/util/FunctionClassUtils.
- Remove redundant files.
- Add FunctionInvoker deprecation annotations.
- Extend the time trigger sample with Retry policies example.
This commit is contained in:
Christian Tzolov
2023-07-14 18:46:42 +02:00
parent 64f57bcef8
commit 6299a5366b
88 changed files with 1834 additions and 1312 deletions

View File

@@ -0,0 +1,11 @@
=== Introduction
Light weight Azure Function forwarding proxy which can deploy any existing Spring Boot web application as Azure Functions.
Infernally uses the Azure Http Trigger mapping.
This module is identified as the only additional dependency to the existing web-app.
A sample is provided in https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure-web[azure-web sample]

View File

@@ -1,12 +0,0 @@
#### Introduction
This module represents a concept of a light weight AWS forwarding proxy which deploys and interacts with existing
Spring Boot web application deployed as AWS Lambda.
A sample is provided in [sample](https://github.com/spring-cloud/spring-cloud-function/tree/serverless-web/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store) directory. It contain README and SAM template file to simplify the deployment. This module is identified as the only additional dependnecy to the existing web-app.
_NOTE: Although this module is AWS specific, this dependency is protocol only (not binary), therefore there is no AWS dependnecies._
The aformentioned proxy is identified as AWS Lambda [handler](https://github.com/spring-cloud/spring-cloud-function/blob/serverless-web/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/template.yml#L14)
The main Spring Boot configuration file is identified as [MAIN_CLASS](https://github.com/spring-cloud/spring-cloud-function/blob/serverless-web/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/template.yml#L22)

View File

@@ -24,10 +24,18 @@
<artifactId>annotations</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-serverless-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.azure.functions</groupId>
<artifactId>azure-functions-java-library</artifactId>

View File

@@ -38,6 +38,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.function.serverless.web.ProxyHttpServletRequest;
import org.springframework.cloud.function.serverless.web.ProxyHttpServletResponse;
import org.springframework.cloud.function.serverless.web.ProxyMvc;
import org.springframework.cloud.function.utils.FunctionClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@@ -61,7 +62,6 @@ public class AzureWebProxyInvoker implements FunctionInstanceInjector {
@Override
public <T> T getInstance(Class<T> functionClass) throws Exception {
// System.setProperty("MAIN_CLASS", "oz.spring.petstore.PetStoreSpringAppConfig");
this.initialize();
return (T) this;
}

View File

@@ -1,153 +0,0 @@
/*
* Copyright 2019-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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.springframework.cloud.function.adapter.azure.web;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
//import org.springframework.boot.SpringBootConfiguration;
//import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.KotlinDetector;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* General utility class which aggregates various class-level utility functions
* used by the framework.
*
* @author Oleg Zhurakousky
* @since 3.0.1
*/
public final class FunctionClassUtils {
private static Log logger = LogFactory.getLog(FunctionClassUtils.class);
private FunctionClassUtils() {
}
/**
* Discovers the start class in the currently running application.
* The discover search order is 'MAIN_CLASS' environment property,
* 'MAIN_CLASS' system property, META-INF/MANIFEST.MF:'Start-Class' attribute,
* meta-inf/manifest.mf:'Start-Class' attribute.
*
* @return instance of Class which represent the start class of the application.
*/
public static Class<?> getStartClass() {
ClassLoader classLoader = FunctionClassUtils.class.getClassLoader();
return getStartClass(classLoader);
}
static Class<?> getStartClass(ClassLoader classLoader) {
Class<?> mainClass = null;
if (System.getenv("MAIN_CLASS") != null) {
mainClass = ClassUtils.resolveClassName(System.getenv("MAIN_CLASS"), classLoader);
}
else if (System.getProperty("MAIN_CLASS") != null) {
mainClass = ClassUtils.resolveClassName(System.getProperty("MAIN_CLASS"), classLoader);
}
else {
try {
Class<?> result = getStartClass(
Collections.list(classLoader.getResources(JarFile.MANIFEST_NAME)), classLoader);
if (result == null) {
result = getStartClass(Collections
.list(classLoader.getResources("meta-inf/manifest.mf")), classLoader);
}
Assert.notNull(result, "Failed to locate main class");
mainClass = result;
}
catch (Exception ex) {
throw new IllegalStateException("Failed to discover main class. An attempt was made to discover "
+ "main class as 'MAIN_CLASS' environment variable, system property as well as "
+ "entry in META-INF/MANIFEST.MF (in that order).", ex);
}
}
logger.info("Main class: " + mainClass);
return mainClass;
}
private static Class<?> getStartClass(List<URL> list, ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Searching manifests: " + list);
}
for (URL url : list) {
try {
InputStream inputStream = null;
Manifest manifest = new Manifest(url.openStream());
logger.info("Searching for start class in manifest: " + url);
if (logger.isDebugEnabled()) {
manifest.write(System.out);
}
try {
String startClassName = manifest.getMainAttributes().getValue("Start-Class");
if (!StringUtils.hasText(startClassName)) {
startClassName = manifest.getMainAttributes().getValue("Main-Class");
}
if (StringUtils.hasText(startClassName)) {
Class<?> startClass = ClassUtils.forName(startClassName, classLoader);
if (KotlinDetector.isKotlinType(startClass)) {
PathMatchingResourcePatternResolver r = new PathMatchingResourcePatternResolver(classLoader);
String packageName = startClass.getPackage().getName();
Resource[] resources = r.getResources("classpath:" + packageName.replace(".", "/") + "/*.class");
for (int i = 0; i < resources.length; i++) {
Resource resource = resources[i];
String className = packageName + "." + (resource.getFilename().replace("/", ".")).replace(".class", "");
startClass = ClassUtils.forName(className, classLoader);
// if (isSpringBootApplication(startClass)) {
// logger.info("Loaded Main Kotlin Class: " + startClass);
// return startClass;
// }
}
}
// else if (isSpringBootApplication(startClass)) {
// logger.info("Loaded Start Class: " + startClass);
// return startClass;
// }
}
}
finally {
if (inputStream != null) {
inputStream.close();
}
}
}
catch (Exception ex) {
logger.debug("Failed to determine Start-Class in manifest file of " + url, ex);
}
}
return null;
}
// private static boolean isSpringBootApplication(Class<?> startClass) {
// return startClass.getDeclaredAnnotation(SpringBootApplication.class) != null
// || startClass.getDeclaredAnnotation(SpringBootConfiguration.class) != null;
// }
}

View File

@@ -0,0 +1 @@
spring.banner.location=classpath:/spring-azure-function-banner.txt

View File

@@ -0,0 +1,8 @@
____ _ _ _____ _ _
/ ___| _ __ _ __(_)_ __ __ _ / \ _____ _ _ __ ___ | ___| _ _ __ ___| |_(_) ___ _ __ ___
\___ \| '_ \| '__| | '_ \ / _` | / _ \ |_ / | | | '__/ _ \ | |_ | | | | '_ \ / __| __| |/ _ \| '_ \/ __|
___) | |_) | | | | | | | (_| | / ___ \ / /| |_| | | | __/ | _|| |_| | | | | (__| |_| | (_) | | | \__ \
|____/| .__/|_| |_|_| |_|\__, | /_/ \_\/___|\__,_|_| \___| |_| \__,_|_| |_|\___|\__|_|\___/|_| |_|___/
|_| |___/
${application.title} ${application.version}
Powered by Spring Boot ${spring-boot.version} and Azure Functions

View File

@@ -7,6 +7,11 @@ Edit the files in the src/main/asciidoc/ directory instead.
This project provides an adapter layer for a Spring Cloud Function application onto Azure.
You can write an app with a single `@Bean` of type `Function` and it will be deployable in Azure if you get the JAR file laid out right.
== Sample Function
== Sample Functions
Go to the link:../../spring-cloud-function-samples/function-sample-azure/[function-sample-azure] to learn about how the sample works, and how to run and test it.
- ../../spring-cloud-function-samples/function-sample-azure-http-trigger[Azure HTTP Trigger (Maven)]
- ../../spring-cloud-function-samples/function-sample-azure-http-trigger-gradle[Azure HTTP Trigger (Gradle)]
- ../../spring-cloud-function-samples/function-sample-azure-blob-trigger[Azure Blob Trigger (Maven)]
- ../../spring-cloud-function-samples/function-sample-azure-timer-trigger[Azure Timer Trigger (Maven)]
- ../../spring-cloud-function-samples/function-sample-azure-kafka-trigger[Azure Kafka Trigger & Output Binding (Maven)]
- ../../spring-cloud-function-samples/function-sample-azure/[(Legacy - FunctionInvoker) Azure HTTP Trigger (Maven)]

View File

@@ -22,10 +22,12 @@ import com.microsoft.azure.functions.spi.inject.FunctionInstanceInjector;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.ResourceBanner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.cloud.function.utils.FunctionClassUtils;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
@@ -96,6 +98,8 @@ public class AzureFunctionInstanceInjector implements FunctionInstanceInjector {
SpringApplication application = new org.springframework.cloud.function.context.FunctionalSpringApplication(
configurationClass);
application.setWebApplicationType(WebApplicationType.NONE);
application.setBanner(new ResourceBanner(
new DefaultResourceLoader().getResource("classpath:/spring-azure-function-banner.txt")));
return application;
}
}

View File

@@ -65,8 +65,14 @@ import org.springframework.util.StringUtils;
* @param <O> result type
* @author Oleg Zhurakousky
* @author Chris Bono
* @author Christian Tzolov
*
* @since 3.2
*
* @deprecated since 4.0.0 in favor of the dependency injection implementation {@link AzureFunctionInstanceInjector}.
* Follow the official documentation for further information.
*/
@Deprecated
public class FunctionInvoker<I, O> {
private static Log logger = LogFactory.getLog(FunctionInvoker.class);
@@ -109,11 +115,13 @@ public class FunctionInvoker<I, O> {
private FunctionInvocationWrapper discoverFunction(String functionDefinition) {
FunctionInvocationWrapper function = FUNCTION_CATALOG.lookup(functionDefinition);
if (function != null && StringUtils.hasText(functionDefinition) && !function.getFunctionDefinition().equals(functionDefinition)) {
if (function != null && StringUtils.hasText(functionDefinition)
&& !function.getFunctionDefinition().equals(functionDefinition)) {
this.registerFunction(functionDefinition);
function = FUNCTION_CATALOG.lookup(functionDefinition);
}
else if (function == null && StringUtils.hasText(functionDefinition) && APPLICATION_CONTEXT.containsBean(functionDefinition)) {
else if (function == null && StringUtils.hasText(functionDefinition)
&& APPLICATION_CONTEXT.containsBean(functionDefinition)) {
this.registerFunction(functionDefinition);
function = FUNCTION_CATALOG.lookup(functionDefinition);
}
@@ -128,20 +136,22 @@ public class FunctionInvoker<I, O> {
Object functionResult = function.apply(enhancedInput);
if (functionResult instanceof Publisher) {
return postProcessReactiveFunctionResult(input, enhancedInput, (Publisher<?>) functionResult, function, executionContext);
return postProcessReactiveFunctionResult(input, enhancedInput, (Publisher<?>) functionResult, function,
executionContext);
}
return postProcessImperativeFunctionResult(input, enhancedInput, functionResult, function, executionContext);
}
/**
* Post-processes the result from a non-reactive function invocation before returning it to the Azure
* runtime. The default behavior is to {@link #convertOutputIfNecessary possibly convert} the result.
* Post-processes the result from a non-reactive function invocation before returning it to the Azure runtime. The
* default behavior is to {@link #convertOutputIfNecessary possibly convert} the result.
*
* <p>Provides a hook for custom function invokers to extend/modify the function results handling.
* <p>
* Provides a hook for custom function invokers to extend/modify the function results handling.
*
* @param rawInputs the inputs passed in from the Azure runtime
* @param functionInputs the actual inputs used for the function invocation; may be
* {@link #enhanceInputIfNecessary different} from the {@literal rawInputs}
* @param functionInputs the actual inputs used for the function invocation; may be {@link #enhanceInputIfNecessary
* different} from the {@literal rawInputs}
* @param functionResult the result from the function invocation
* @param function the invoked function
* @param executionContext the Azure execution context
@@ -149,44 +159,47 @@ public class FunctionInvoker<I, O> {
*/
@SuppressWarnings("unchecked")
protected O postProcessImperativeFunctionResult(I rawInputs, Object functionInputs, Object functionResult,
FunctionInvocationWrapper function, ExecutionContext executionContext
) {
FunctionInvocationWrapper function, ExecutionContext executionContext) {
return (O) this.convertOutputIfNecessary(rawInputs, functionResult);
}
/**
* Post-processes the result from a reactive function invocation before returning it to the Azure
* runtime. The default behavior is to delegate to {@link #postProcessMonoFunctionResult} or
* Post-processes the result from a reactive function invocation before returning it to the Azure runtime. The
* default behavior is to delegate to {@link #postProcessMonoFunctionResult} or
* {@link #postProcessFluxFunctionResult} based on the result type.
*
* <p>Provides a hook for custom function invokers to extend/modify the function results handling.
* <p>
* Provides a hook for custom function invokers to extend/modify the function results handling.
*
* @param rawInputs the inputs passed in from the Azure runtime
* @param functionInputs the actual inputs used for the function invocation; may be
* {@link #enhanceInputIfNecessary different} from the {@literal rawInputs}
* @param functionInputs the actual inputs used for the function invocation; may be {@link #enhanceInputIfNecessary
* different} from the {@literal rawInputs}
* @param functionResult the result from the function invocation
* @param function the invoked function
* @param executionContext the Azure execution context
* @return the possibly modified function results
*/
protected O postProcessReactiveFunctionResult(I rawInputs, Object functionInputs, Publisher<?> functionResult,
FunctionInvocationWrapper function, ExecutionContext executionContext
) {
FunctionInvocationWrapper function, ExecutionContext executionContext) {
if (FunctionTypeUtils.isMono(function.getOutputType())) {
return postProcessMonoFunctionResult(rawInputs, functionInputs, Mono.from(functionResult), function, executionContext);
return postProcessMonoFunctionResult(rawInputs, functionInputs, Mono.from(functionResult), function,
executionContext);
}
return postProcessFluxFunctionResult(rawInputs, functionInputs, Flux.from(functionResult), function, executionContext);
return postProcessFluxFunctionResult(rawInputs, functionInputs, Flux.from(functionResult), function,
executionContext);
}
/**
* Post-processes the {@code Mono} result from a reactive function invocation before returning it to the Azure
* runtime. The default behavior is to {@link Mono#blockOptional()} and {@link #convertOutputIfNecessary possibly convert} the result.
* runtime. The default behavior is to {@link Mono#blockOptional()} and {@link #convertOutputIfNecessary possibly
* convert} the result.
*
* <p>Provides a hook for custom function invokers to extend/modify the function results handling.
* <p>
* Provides a hook for custom function invokers to extend/modify the function results handling.
*
* @param rawInputs the inputs passed in from the Azure runtime
* @param functionInputs the actual inputs used for the function invocation; may be
* {@link #enhanceInputIfNecessary different} from the {@literal rawInputs}
* @param functionInputs the actual inputs used for the function invocation; may be {@link #enhanceInputIfNecessary
* different} from the {@literal rawInputs}
* @param functionResult the Mono result from the function invocation
* @param function the invoked function
* @param executionContext the Azure execution context
@@ -194,20 +207,21 @@ public class FunctionInvoker<I, O> {
*/
@SuppressWarnings("unchecked")
protected O postProcessMonoFunctionResult(I rawInputs, Object functionInputs, Mono<?> functionResult,
FunctionInvocationWrapper function, ExecutionContext executionContext
) {
FunctionInvocationWrapper function, ExecutionContext executionContext) {
return (O) this.convertOutputIfNecessary(rawInputs, functionResult.blockOptional().get());
}
/**
* Post-processes the {@code Flux} result from a reactive function invocation before returning it to the Azure
* runtime. The default behavior is to {@link Flux#toIterable() block} and {@link #convertOutputIfNecessary possibly convert} the results.
* runtime. The default behavior is to {@link Flux#toIterable() block} and {@link #convertOutputIfNecessary possibly
* convert} the results.
*
* <p>Provides a hook for custom function invokers to extend/modify the function results handling.
* <p>
* Provides a hook for custom function invokers to extend/modify the function results handling.
*
* @param rawInputs the inputs passed in from the Azure runtime
* @param functionInputs the actual inputs used for the function invocation; may be
* {@link #enhanceInputIfNecessary different} from the {@literal rawInputs}
* @param functionInputs the actual inputs used for the function invocation; may be {@link #enhanceInputIfNecessary
* different} from the {@literal rawInputs}
* @param functionResult the Mono result from the function invocation
* @param function the invoked function
* @param executionContext the Azure execution context
@@ -215,16 +229,16 @@ public class FunctionInvoker<I, O> {
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected O postProcessFluxFunctionResult(I rawInputs, Object functionInputs, Flux<?> functionResult,
FunctionInvocationWrapper function, ExecutionContext executionContext
) {
FunctionInvocationWrapper function, ExecutionContext executionContext) {
List resultList = new ArrayList<>();
for (Object resultItem : functionResult.toIterable()) {
if (resultItem instanceof Collection) {
resultList.addAll((Collection) resultItem);
}
else {
if (!function.isSupplier() && Collection.class.isAssignableFrom(FunctionTypeUtils.getRawType(function.getInputType()))
&& !Collection.class.isAssignableFrom(FunctionTypeUtils.getRawType(function.getOutputType()))) {
if (!function.isSupplier()
&& Collection.class.isAssignableFrom(FunctionTypeUtils.getRawType(function.getInputType()))
&& !Collection.class.isAssignableFrom(FunctionTypeUtils.getRawType(function.getOutputType()))) {
return (O) this.convertOutputIfNecessary(rawInputs, resultItem);
}
else {
@@ -238,11 +252,10 @@ public class FunctionInvoker<I, O> {
@SuppressWarnings({ "unchecked", "rawtypes" })
private void registerFunction(String functionDefinition) {
if (APPLICATION_CONTEXT.containsBean(functionDefinition)) {
FunctionRegistration functionRegistration =
new FunctionRegistration(APPLICATION_CONTEXT.getBean(functionDefinition), functionDefinition);
FunctionRegistration functionRegistration = new FunctionRegistration(
APPLICATION_CONTEXT.getBean(functionDefinition), functionDefinition);
Type type = FunctionContextUtils.
findType(functionDefinition, APPLICATION_CONTEXT.getBeanFactory());
Type type = FunctionContextUtils.findType(functionDefinition, APPLICATION_CONTEXT.getBeanFactory());
functionRegistration = functionRegistration.type(type);
@@ -305,8 +318,9 @@ public class FunctionInvoker<I, O> {
MessageBuilder<?> messageBuilder = null;
if (input instanceof HttpRequestMessage) {
HttpRequestMessage<I> requestMessage = (HttpRequestMessage<I>) input;
Object payload = requestMessage.getHttpMethod() != null && requestMessage.getHttpMethod().equals(HttpMethod.GET)
? requestMessage.getQueryParameters()
Object payload = requestMessage.getHttpMethod() != null
&& requestMessage.getHttpMethod().equals(HttpMethod.GET)
? requestMessage.getQueryParameters()
: requestMessage.getBody();
if (payload == null) {
@@ -352,8 +366,10 @@ public class FunctionInvoker<I, O> {
if (CollectionUtils.isEmpty(mf)) {
OBJECT_MAPPER = new JacksonMapper(new ObjectMapper());
JsonMessageConverter jsonConverter = new JsonMessageConverter(OBJECT_MAPPER);
SmartCompositeMessageConverter messageConverter = new SmartCompositeMessageConverter(Collections.singletonList(jsonConverter));
FUNCTION_CATALOG = new SimpleFunctionRegistry(APPLICATION_CONTEXT.getBeanFactory().getConversionService(),
SmartCompositeMessageConverter messageConverter = new SmartCompositeMessageConverter(
Collections.singletonList(jsonConverter));
FUNCTION_CATALOG = new SimpleFunctionRegistry(
APPLICATION_CONTEXT.getBeanFactory().getConversionService(),
messageConverter, OBJECT_MAPPER);
}
else {

View File

@@ -29,7 +29,11 @@ import com.microsoft.azure.functions.HttpResponseMessage;
* @author Oleg Zhurakousky
*
* @since 3.2
*
* @deprecated since 4.0.0 in favor of the dependency injection implementation {@link AzureFunctionInstanceInjector}.
* Follow the official documentation for further information.
*/
@Deprecated
public class HttpFunctionInvoker<I> extends
FunctionInvoker<HttpRequestMessage<I>, HttpResponseMessage> {

View File

@@ -0,0 +1,8 @@
____ _ _ _____ _ _
/ ___| _ __ _ __(_)_ __ __ _ / \ _____ _ _ __ ___ | ___| _ _ __ ___| |_(_) ___ _ __ ___
\___ \| '_ \| '__| | '_ \ / _` | / _ \ |_ / | | | '__/ _ \ | |_ | | | | '_ \ / __| __| |/ _ \| '_ \/ __|
___) | |_) | | | | | | | (_| | / ___ \ / /| |_| | | | __/ | _|| |_| | | | | (__| |_| | (_) | | | \__ \
|____/| .__/|_| |_|_| |_|\__, | /_/ \_\/___|\__,_|_| \___| |_| \__,_|_| |_|\___|\__|_|\___/|_| |_|___/
|_| |___/
${application.title} ${application.version}
Powered by Spring Boot ${spring-boot.version} and Azure Functions

View File

@@ -136,7 +136,7 @@ public class ProxyHttpServletResponse implements HttpServletResponse {
@Override
public void setContentLengthLong(long len) {
throw new UnsupportedOperationException();
// Ignore
}
@Override