org.nervousync.utils.ServiceUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of utils-jdk11 Show documentation
Show all versions of utils-jdk11 Show documentation
Java utility collections, development by Nervousync Studio (NSYC)
/*
* Licensed to the Nervousync Studio (NSYC) 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.nervousync.utils;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.ws.rs.*;
import jakarta.ws.rs.client.*;
import jakarta.ws.rs.core.*;
import jakarta.xml.ws.Service;
import jakarta.xml.ws.WebServiceClient;
import jakarta.xml.ws.handler.HandlerResolver;
import org.nervousync.commons.core.Globals;
import org.nervousync.enumerations.web.HttpMethodOption;
import org.nervousync.restful.converter.ParameterConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.namespace.QName;
import javax.xml.rpc.ServiceException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.*;
/**
* The type Service utils.
*/
public final class ServiceUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceUtils.class);
private static final List REGISTERED_CONVERTERS;
static {
REGISTERED_CONVERTERS = new ArrayList<>();
ServiceLoader.load(ParameterConverter.class).forEach(ServiceUtils::registerConverter);
}
private ServiceUtils() {
}
/**
* Generate SOAP client instance
*
* @param End point interface
* @param serviceInterface End point interface
* @param handlerResolver Handler resolver
* @return Generated instance
* @throws MalformedURLException if no protocol is specified, or an unknown protocol is found, or spec is null.
*/
public static T SOAPClient(final Class serviceInterface, final HandlerResolver handlerResolver)
throws MalformedURLException {
if (!serviceInterface.isAnnotationPresent(WebServiceClient.class)) {
return null;
}
WebServiceClient serviceClient = serviceInterface.getAnnotation(WebServiceClient.class);
String namespaceURI = serviceClient.targetNamespace();
String serviceName = serviceClient.name();
URL wsdlLocation = new URL(serviceClient.wsdlLocation());
if (namespaceURI.length() == 0) {
String packageName = serviceInterface.getPackage().getName();
String[] packageNames = StringUtils.tokenizeToStringArray(packageName, ".");
StringBuilder stringBuilder = new StringBuilder(wsdlLocation.getProtocol() + "://");
for (int i = packageNames.length - 1; i >= 0; i--) {
stringBuilder.append(packageNames[i]).append(".");
}
namespaceURI = stringBuilder.substring(0, stringBuilder.length() - 1) + "/";
}
if (StringUtils.isEmpty(serviceName)) {
serviceName = serviceInterface.getSimpleName() + "Service";
}
Service service = Service.create(wsdlLocation, new QName(namespaceURI, serviceName));
if (handlerResolver != null) {
service.setHandlerResolver(handlerResolver);
}
return service.getPort(new QName(namespaceURI, serviceName), serviceInterface);
}
/**
* Generate Restful service client instance
*
* @param Client interface
* @param targetAddress the target address
* @param serviceClient Client interface class
* @return Generated instance
*/
public static T RestfulClient(final String targetAddress, final Class serviceClient) {
return RestfulClient(targetAddress, serviceClient, null);
}
/**
* Generate Restful service client instance
*
* @param Client interface
* @param targetAddress the target address
* @param serviceClient Client interface class
* @param headerMap the header map
* @return Generated instance
*/
public static T RestfulClient(final String targetAddress, final Class serviceClient,
final Map headerMap) {
if (StringUtils.isEmpty(targetAddress)) {
return null;
}
String servicePath = targetAddress.toLowerCase().startsWith("http")
? targetAddress
: Globals.HTTP_PROTOCOL + targetAddress;
if (serviceClient.isAnnotationPresent(Path.class)) {
servicePath += serviceClient.getAnnotation(Path.class).value();
}
return ObjectUtils.newInstance(serviceClient, new RestfulInterceptor(servicePath, headerMap));
}
/**
* Register converter.
*
* @param parameterConverter the parameter converter
*/
public static void registerConverter(final ParameterConverter parameterConverter) {
if (REGISTERED_CONVERTERS.contains(parameterConverter)) {
LOGGER.warn("Exists converter: {}", parameterConverter.getClass().getName());
}
REGISTERED_CONVERTERS.add(parameterConverter);
}
/**
* Init converter optional.
*
* @param targetClass the target class
* @return the optional
*/
public static Optional initConverter(final Class> targetClass) {
return REGISTERED_CONVERTERS
.stream()
.filter(parameterConverter -> parameterConverter.match(targetClass))
.findFirst();
}
private static final class RestfulInterceptor implements InvocationHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final String requestPath;
private final Map headerMap;
/**
* Instantiates a new Restful interceptor.
*
* @param requestPath the request path
* @param headerMap the header map
*/
RestfulInterceptor(final String requestPath, final Map headerMap) {
this.requestPath = requestPath;
this.headerMap = new HashMap<>();
if (headerMap != null) {
this.headerMap.putAll(headerMap);
}
}
@Override
public Object invoke(final Object o, final Method method, final Object[] objects) throws Throwable {
HttpMethodOption methodOption = RequestUtils.httpMethodOption(method);
if (HttpMethodOption.UNKNOWN.equals(methodOption) || !method.isAnnotationPresent(Path.class)) {
throw new Exception("Unknown method! ");
}
String methodName = method.getAnnotation(Path.class).value();
if (methodName.length() == 0) {
methodName = method.getName();
} else if (methodName.startsWith("/")) {
methodName = methodName.substring(1);
}
String servicePath = this.requestPath + "/" + methodName;
Annotation[][] annotations = method.getParameterAnnotations();
Class>[] parameterClasses = method.getParameterTypes();
if (objects.length != parameterClasses.length) {
throw new Exception("Mismatch arguments");
}
Map formParameters = new HashMap<>();
Map queryParameters = new HashMap<>();
Map matrixParameters = new HashMap<>();
String[] mediaTypes = method.isAnnotationPresent(Consumes.class)
? method.getAnnotation(Consumes.class).value()
: new String[0];
for (int i = 0; i < objects.length; i++) {
Object paramObj = objects[i];
if (paramObj == null) {
continue;
}
if (Arrays.stream(annotations[i])
.anyMatch(annotation -> annotation.annotationType().equals(BeanParam.class))) {
BeanParameter beanParameter = new BeanParameter(paramObj, mediaTypes);
this.headerMap.putAll(beanParameter.getHeaders());
for (Map.Entry entry : beanParameter.getPaths().entrySet()) {
if (StringUtils.isEmpty(entry.getKey()) || entry.getValue() == null) {
throw new ServiceException("Unknown parameter name or path parameter value is null! ");
}
String pathKey = "{" + entry.getKey() + "}";
if (servicePath.indexOf(pathKey) > 0) {
servicePath = StringUtils.replace(servicePath, pathKey,
URLEncoder.encode(entry.getValue(), Globals.DEFAULT_ENCODING));
}
}
formParameters.putAll(beanParameter.getFormParameters());
queryParameters.putAll(beanParameter.getQueryParameters());
matrixParameters.putAll(beanParameter.getMatrixParameters());
} else if (Arrays.stream(annotations[i])
.anyMatch(annotation -> annotation.annotationType().equals(MatrixParam.class))) {
Arrays.stream(annotations[i])
.filter(annotation -> annotation.annotationType().equals(MatrixParam.class))
.findFirst()
.map(annotation -> ((MatrixParam) annotation).value())
.ifPresent(paramName -> {
if (paramObj.getClass().isArray()) {
Arrays.asList((Object[]) paramObj).forEach(itemValue -> {
String paramValue = initConverter(itemValue.getClass())
.map(parameterConverter ->
parameterConverter.toString(itemValue, mediaTypes))
.orElse(itemValue.toString());
matrixParameters.put(paramName,
appendValue(matrixParameters.getOrDefault(paramName, new String[0]),
paramValue));
});
} else if (List.class.isAssignableFrom(paramObj.getClass())) {
((List>) paramObj).forEach(itemValue -> {
String paramValue = initConverter(itemValue.getClass())
.map(parameterConverter ->
parameterConverter.toString(itemValue, mediaTypes))
.orElse(itemValue.toString());
matrixParameters.put(paramName,
appendValue(matrixParameters.getOrDefault(paramName, new String[0]),
paramValue));
});
} else {
String paramValue = initConverter(paramObj.getClass())
.map(parameterConverter ->
parameterConverter.toString(paramObj, mediaTypes))
.orElse(paramObj.toString());
matrixParameters.put(paramName,
appendValue(matrixParameters.getOrDefault(paramName, new String[0]),
paramValue));
}
});
} else {
String paramValue = initConverter(paramObj.getClass())
.map(parameterConverter -> parameterConverter.toString(paramObj, mediaTypes))
.orElse(paramObj.toString());
if (Arrays.stream(annotations[i])
.anyMatch(annotation -> annotation.annotationType().equals(QueryParam.class))) {
String paramName =
Arrays.stream(annotations[i]).filter(annotation ->
annotation.annotationType().equals(QueryParam.class))
.findFirst()
.map(annotation -> ((QueryParam) annotation).value())
.orElse(Globals.DEFAULT_VALUE_STRING);
if (StringUtils.notBlank(paramName)) {
queryParameters.put(paramName, paramValue);
}
}
if (Arrays.stream(annotations[i])
.anyMatch(annotation -> annotation.annotationType().equals(FormParam.class))) {
String paramName =
Arrays.stream(annotations[i]).filter(annotation ->
annotation.annotationType().equals(FormParam.class))
.findFirst()
.map(annotation -> ((FormParam) annotation).value())
.orElse(Globals.DEFAULT_VALUE_STRING);
if (StringUtils.notBlank(paramName)) {
queryParameters.put(paramName, paramValue);
}
}
if (Arrays.stream(annotations[i])
.anyMatch(annotation -> annotation.annotationType().equals(PathParam.class))) {
String paramName =
Arrays.stream(annotations[i]).filter(annotation ->
annotation.annotationType().equals(PathParam.class))
.findFirst()
.map(annotation -> ((PathParam) annotation).value())
.orElse(Globals.DEFAULT_VALUE_STRING);
if (StringUtils.notBlank(paramName)) {
if (StringUtils.isEmpty(paramValue)) {
throw new ServiceException("Unknown parameter name or path parameter value is null! ");
}
String pathKey = "{" + paramName + "}";
if (servicePath.indexOf(pathKey) > 0) {
servicePath = StringUtils.replace(servicePath, pathKey,
URLEncoder.encode(paramValue, Globals.DEFAULT_ENCODING));
}
}
}
if (Arrays.stream(annotations[i])
.anyMatch(annotation -> annotation.annotationType().equals(HeaderParam.class))) {
String paramName =
Arrays.stream(annotations[i]).filter(annotation ->
annotation.annotationType().equals(HeaderParam.class))
.findFirst()
.map(annotation -> ((HeaderParam) annotation).value())
.orElse(Globals.DEFAULT_VALUE_STRING);
if (StringUtils.notBlank(paramName)) {
this.headerMap.put(paramName, paramValue);
}
}
}
}
Form form = null;
if (HttpMethodOption.POST.equals(methodOption)
|| HttpMethodOption.PUT.equals(methodOption)
|| HttpMethodOption.PATCH.equals(methodOption)) {
form = new Form();
formParameters.forEach(form::param);
}
try (Client client = ClientBuilder.newClient()) {
WebTarget webTarget = client.target(servicePath);
queryParameters.forEach(webTarget::queryParam);
matrixParameters.forEach(webTarget::matrixParam);
String[] acceptTypes = method.isAnnotationPresent(Produces.class)
? method.getAnnotation(Produces.class).value()
: new String[]{"*/*"};
if (this.logger.isDebugEnabled()) {
this.logger.debug("Accept data types: {}", String.join(",", acceptTypes));
}
Invocation.Builder builder = webTarget.request(acceptTypes);
if (method.isAnnotationPresent(Consumes.class)) {
builder.accept(method.getAnnotation(Consumes.class).value());
}
this.headerMap.forEach(builder::header);
return this.execute(methodOption, builder, form, method);
}
}
private Response initResponse(final HttpMethodOption methodOption, final Invocation.Builder builder,
final Form form) throws ServiceException {
switch (methodOption) {
case GET:
return builder.get();
case PATCH:
return builder.method("PATCH",
Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
case PUT:
return builder.put(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
case POST:
return builder.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
case DELETE:
return builder.delete();
case HEAD:
return builder.head();
default:
throw new ServiceException("Method not supported! ");
}
}
private Object execute(final HttpMethodOption methodOption, final Invocation.Builder builder,
final Form form, final Method method) throws ServiceException {
try (Response response = this.initResponse(methodOption, builder, form)) {
boolean operateResult;
switch (methodOption) {
case PUT:
operateResult = (response.getStatus() == HttpServletResponse.SC_CREATED
|| response.getStatus() == HttpServletResponse.SC_NO_CONTENT
|| response.getStatus() == HttpServletResponse.SC_OK);
break;
case POST:
operateResult = (response.getStatus() == HttpServletResponse.SC_CREATED
|| response.getStatus() == HttpServletResponse.SC_OK);
break;
case PATCH:
case DELETE:
operateResult = (response.getStatus() == HttpServletResponse.SC_NO_CONTENT);
break;
default:
operateResult = (response.getStatus() == HttpServletResponse.SC_OK);
break;
}
if (operateResult) {
if (response.getStatus() == HttpServletResponse.SC_NO_CONTENT) {
return null;
}
Class> returnType = method.getReturnType();
if (void.class.equals(returnType)) {
return null;
}
Class> paramClass = ReflectionUtils.componentType(method);
String responseData = response.readEntity(String.class);
if (responseData.endsWith(FileUtils.CRLF)) {
responseData = responseData.substring(0, responseData.length() - FileUtils.CRLF.length());
}
if (responseData.endsWith(Character.toString(FileUtils.CR))) {
responseData = responseData.substring(0, responseData.length() - Character.toString(FileUtils.CR).length());
}
if (responseData.endsWith(Character.toString(FileUtils.LF))) {
responseData = responseData.substring(0, responseData.length() - Character.toString(FileUtils.LF).length());
}
switch (response.getHeaderString(HttpHeaders.CONTENT_TYPE)) {
case FileUtils.MIME_TYPE_JSON:
if (returnType.isArray()) {
return parseToList(responseData, paramClass).toArray();
} else if (List.class.isAssignableFrom(returnType)) {
return parseToList(responseData, paramClass);
}
return StringUtils.stringToObject(responseData, returnType);
case FileUtils.MIME_TYPE_TEXT_XML:
case FileUtils.MIME_TYPE_XML:
case FileUtils.MIME_TYPE_TEXT_YAML:
case FileUtils.MIME_TYPE_YAML:
return StringUtils.stringToObject(responseData, returnType);
case FileUtils.MIME_TYPE_TEXT:
return responseData;
default:
final String value = responseData;
return initConverter(returnType)
.map(parameterConverter -> parameterConverter.fromString(returnType, value))
.orElse(null);
}
} else {
String errorMsg = response.readEntity(String.class);
if (this.logger.isDebugEnabled()) {
if (response.getStatus() == HttpServletResponse.SC_BAD_REQUEST) {
errorMsg += "Send request data error!";
} else if (HttpMethodOption.GET.equals(methodOption)
&& response.getStatus() == HttpServletResponse.SC_NOT_FOUND) {
errorMsg += "Not found data! ";
} else if (response.getStatus() == HttpServletResponse.SC_UNAUTHORIZED) {
errorMsg += "Unauthenticated error! ";
} else if (response.getStatus() == HttpServletResponse.SC_FORBIDDEN) {
errorMsg += "Request forbidden! ";
} else if (response.getStatus() == HttpServletResponse.SC_BAD_GATEWAY
|| response.getStatus() == HttpServletResponse.SC_SERVICE_UNAVAILABLE
|| response.getStatus() == HttpServletResponse.SC_GATEWAY_TIMEOUT) {
errorMsg += "Request forbidden! ";
} else {
errorMsg += Globals.DEFAULT_VALUE_STRING;
}
this.logger.debug("Response code: {}, error message: {}", response.getStatus(), errorMsg);
}
throw new ServiceException(errorMsg);
}
} catch (Exception e) {
if (e instanceof ServiceException) {
throw e;
}
throw new ServiceException(e);
}
}
}
private static final class BeanParameter {
/**
* The Form parameters.
*/
final Map formParameters = new HashMap<>();
/**
* The Query parameters.
*/
final Map queryParameters = new HashMap<>();
/**
* The Parameters.
*/
final Map matrixParameters = new HashMap<>();
/**
* The Headers.
*/
final Map headers = new HashMap<>();
/**
* The Paths.
*/
final Map paths = new HashMap<>();
/**
* Instantiates a new Bean parameter.
*
* @param beanObject the bean object
*/
BeanParameter(final Object beanObject, final String[] mediaTypes) {
ReflectionUtils.getAllDeclaredFields(beanObject.getClass()).forEach(field -> {
Object fieldValue = ReflectionUtils.getFieldValue(field, beanObject);
if (field.isAnnotationPresent(BeanParam.class)) {
BeanParameter beanParameter = new BeanParameter(fieldValue, mediaTypes);
this.formParameters.putAll(beanParameter.getFormParameters());
this.queryParameters.putAll(beanParameter.getQueryParameters());
this.matrixParameters.putAll(beanParameter.getMatrixParameters());
this.headers.putAll(beanParameter.getHeaders());
this.paths.putAll(beanParameter.getPaths());
} else {
String stringValue = initConverter(fieldValue.getClass())
.map(parameterConverter -> parameterConverter.toString(fieldValue, mediaTypes))
.orElse(fieldValue.toString());
if (field.isAnnotationPresent(QueryParam.class)) {
this.queryParameters.put(field.getAnnotation(QueryParam.class).value(), stringValue);
} else if (field.isAnnotationPresent(FormParam.class)) {
this.formParameters.put(field.getAnnotation(FormParam.class).value(), stringValue);
} else if (field.isAnnotationPresent(MatrixParam.class)) {
String paramName = field.getAnnotation(MatrixParam.class).value();
String[] paramValues = this.matrixParameters.getOrDefault(paramName, new String[0]);
this.matrixParameters.put(paramName, appendValue(paramValues, stringValue));
} else if (field.isAnnotationPresent(HeaderParam.class)) {
this.headers.put(field.getAnnotation(HeaderParam.class).value(), stringValue);
} else if (field.isAnnotationPresent(PathParam.class)) {
this.paths.put(field.getAnnotation(HeaderParam.class).value(), stringValue);
}
}
});
}
/**
* Gets form parameters.
*
* @return the form parameters
*/
public Map getFormParameters() {
return formParameters;
}
/**
* Gets query parameters.
*
* @return the query parameters
*/
public Map getQueryParameters() {
return queryParameters;
}
/**
* Gets matrix parameters.
*
* @return the matrix parameters
*/
public Map getMatrixParameters() {
return matrixParameters;
}
/**
* Gets headers.
*
* @return the headers
*/
public Map getHeaders() {
return headers;
}
/**
* Gets paths.
*
* @return the paths
*/
public Map getPaths() {
return paths;
}
}
private static List parseToList(String string, Class targetClass) {
return StringUtils.stringToList(string, Globals.DEFAULT_ENCODING, targetClass);
}
private static String[] appendValue(String[] paramValues, String appendValue) {
String[] newValues = Arrays.copyOf(paramValues, paramValues.length + 1);
newValues[paramValues.length] = Objects.requireNonNullElse(appendValue, Globals.DEFAULT_VALUE_STRING);
return newValues;
}
}