Skip to content

Commit 7b4ab0d

Browse files
authored
Add support of RESTful services (#872)
1 parent b0cc785 commit 7b4ab0d

File tree

29 files changed

+988
-132
lines changed

29 files changed

+988
-132
lines changed

services-api/src/main/java/io/scalecube/services/Reflect.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import io.scalecube.services.annotations.ExecuteOn;
99
import io.scalecube.services.annotations.RequestType;
1010
import io.scalecube.services.annotations.ResponseType;
11+
import io.scalecube.services.annotations.RestMethod;
1112
import io.scalecube.services.annotations.Service;
1213
import io.scalecube.services.annotations.ServiceMethod;
1314
import io.scalecube.services.annotations.Tag;
@@ -176,7 +177,8 @@ public static Map<Method, MethodInfo> methodsInfo(Class<?> serviceInterface) {
176177
requestType(method),
177178
isRequestTypeServiceMessage(method),
178179
isSecured(method),
179-
null))));
180+
null,
181+
restMethod(method)))));
180182
}
181183

182184
/**
@@ -277,6 +279,11 @@ public static String methodName(Method method) {
277279
return methodAnnotation.value().length() > 0 ? methodAnnotation.value() : method.getName();
278280
}
279281

282+
public static String restMethod(Method method) {
283+
RestMethod methodAnnotation = method.getAnnotation(RestMethod.class);
284+
return methodAnnotation != null ? methodAnnotation.value() : null;
285+
}
286+
280287
/**
281288
* Handy method to get qualifier String from service's interface and method.
282289
*

services-api/src/main/java/io/scalecube/services/ServiceCall.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ public Mono<ServiceMessage> requestOne(ServiceMessage request, Type responseType
192192
() -> {
193193
ServiceMethodInvoker methodInvoker;
194194
if (serviceRegistry != null
195-
&& (methodInvoker = serviceRegistry.getInvoker(request.qualifier())) != null) {
195+
&& (methodInvoker = serviceRegistry.getInvoker(request)) != null) {
196196
// local service
197197
return methodInvoker.invokeOne(request).map(this::throwIfError);
198198
} else {
@@ -248,7 +248,7 @@ public Flux<ServiceMessage> requestMany(ServiceMessage request, Type responseTyp
248248
() -> {
249249
ServiceMethodInvoker methodInvoker;
250250
if (serviceRegistry != null
251-
&& (methodInvoker = serviceRegistry.getInvoker(request.qualifier())) != null) {
251+
&& (methodInvoker = serviceRegistry.getInvoker(request)) != null) {
252252
// local service
253253
return methodInvoker.invokeMany(request).map(this::throwIfError);
254254
} else {
@@ -303,7 +303,7 @@ public Flux<ServiceMessage> requestBidirectional(
303303
ServiceMessage request = first.get();
304304
ServiceMethodInvoker methodInvoker;
305305
if (serviceRegistry != null
306-
&& (methodInvoker = serviceRegistry.getInvoker(request.qualifier())) != null) {
306+
&& (methodInvoker = serviceRegistry.getInvoker(request)) != null) {
307307
// local service
308308
return methodInvoker.invokeBidirectional(messages).map(this::throwIfError);
309309
} else {
@@ -412,10 +412,7 @@ private ServiceMessage toServiceMessage(MethodInfo methodInfo, Object request) {
412412

413413
private static ServiceUnavailableException noReachableMemberException(ServiceMessage request) {
414414
return new ServiceUnavailableException(
415-
"No reachable member with such service: "
416-
+ request.qualifier()
417-
+ ", failed request: "
418-
+ request);
415+
"No reachable member with such service: " + request.qualifier());
419416
}
420417

421418
/**

services-api/src/main/java/io/scalecube/services/ServiceMethodDefinition.java

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.io.IOException;
55
import java.io.ObjectInput;
66
import java.io.ObjectOutput;
7+
import java.lang.reflect.Method;
78
import java.util.Collections;
89
import java.util.HashMap;
910
import java.util.Map;
@@ -22,6 +23,7 @@ public class ServiceMethodDefinition implements Externalizable {
2223
private String action;
2324
private Map<String, String> tags;
2425
private boolean isSecured;
26+
private String restMethod;
2527

2628
/**
2729
* Constructor for de/serialization purpose.
@@ -32,25 +34,42 @@ public class ServiceMethodDefinition implements Externalizable {
3234
public ServiceMethodDefinition() {}
3335

3436
/**
35-
* Create a new Service Method Definition.
37+
* Constructor.
3638
*
3739
* @param action method name
3840
*/
3941
public ServiceMethodDefinition(String action) {
40-
this(action, Collections.emptyMap(), false);
42+
this(action, Collections.emptyMap(), false, null);
4143
}
4244

4345
/**
44-
* Create a new Service Method Definition.
46+
* Constructor.
4547
*
4648
* @param action method name
4749
* @param tags tags of this method
4850
* @param isSecured is method protected by authentication
51+
* @param restMethod REST method (optional)
4952
*/
50-
public ServiceMethodDefinition(String action, Map<String, String> tags, boolean isSecured) {
53+
public ServiceMethodDefinition(
54+
String action, Map<String, String> tags, boolean isSecured, String restMethod) {
5155
this.action = Objects.requireNonNull(action, "ServiceMethodDefinition.action is required");
5256
this.tags = Collections.unmodifiableMap(new HashMap<>(tags));
5357
this.isSecured = isSecured;
58+
this.restMethod = restMethod;
59+
}
60+
61+
/**
62+
* Factory method that creates {@link ServiceMethodDefinition} instance from the service method.
63+
*
64+
* @param method servuce method
65+
* @return {@link ServiceMethodDefinition} instance
66+
*/
67+
public static ServiceMethodDefinition fromMethod(Method method) {
68+
return new ServiceMethodDefinition(
69+
Reflect.methodName(method),
70+
Reflect.serviceMethodTags(method),
71+
Reflect.isSecured(method),
72+
Reflect.restMethod(method));
5473
}
5574

5675
public String action() {
@@ -65,12 +84,17 @@ public boolean isSecured() {
6584
return isSecured;
6685
}
6786

87+
public String restMethod() {
88+
return restMethod;
89+
}
90+
6891
@Override
6992
public String toString() {
7093
return new StringJoiner(", ", ServiceMethodDefinition.class.getSimpleName() + "[", "]")
71-
.add("action=" + action)
94+
.add("action='" + action + "'")
7295
.add("tags=" + tags)
7396
.add("isSecured=" + isSecured)
97+
.add("restMethod='" + restMethod + "'")
7498
.toString();
7599
}
76100

@@ -88,6 +112,9 @@ public void writeExternal(ObjectOutput out) throws IOException {
88112

89113
// auth
90114
out.writeBoolean(isSecured);
115+
116+
// rest method
117+
out.writeUTF(restMethod != null ? restMethod : "");
91118
}
92119

93120
@Override
@@ -107,5 +134,9 @@ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundExcept
107134

108135
// auth
109136
this.isSecured = in.readBoolean();
137+
138+
// rest method
139+
final var restMethod = in.readUTF();
140+
this.restMethod = !restMethod.isEmpty() ? restMethod : null;
110141
}
111142
}

services-api/src/main/java/io/scalecube/services/ServiceReference.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class ServiceReference {
2323
private final Map<String, String> tags;
2424
private final Address address;
2525
private final boolean isSecured;
26+
private final String restMethod;
2627

2728
/**
2829
* Constructor for service reference.
@@ -44,6 +45,7 @@ public ServiceReference(
4445
this.tags = mergeTags(serviceMethodDefinition, serviceRegistration, serviceEndpoint);
4546
this.address = serviceEndpoint.address();
4647
this.isSecured = serviceMethodDefinition.isSecured();
48+
this.restMethod = serviceMethodDefinition.restMethod();
4749
}
4850

4951
public String endpointId() {
@@ -82,6 +84,10 @@ public boolean isSecured() {
8284
return isSecured;
8385
}
8486

87+
public String restMethod() {
88+
return restMethod;
89+
}
90+
8591
private Map<String, String> mergeTags(
8692
ServiceMethodDefinition serviceMethodDefinition,
8793
ServiceRegistration serviceRegistration,
@@ -105,6 +111,7 @@ public String toString() {
105111
.add("tags=" + tags)
106112
.add("address=" + address)
107113
.add("isSecured=" + isSecured)
114+
.add("restMethod='" + restMethod + "'")
108115
.toString();
109116
}
110117
}

services-api/src/main/java/io/scalecube/services/ServiceScanner.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,7 @@ public static List<ServiceRegistration> scanServiceInfo(ServiceInfo serviceInfo)
3939
List<ServiceMethodDefinition> actions =
4040
Arrays.stream(serviceInterface.getMethods())
4141
.filter(method -> method.isAnnotationPresent(ServiceMethod.class))
42-
.map(
43-
method ->
44-
new ServiceMethodDefinition(
45-
Reflect.methodName(method),
46-
Reflect.serviceMethodTags(method),
47-
Reflect.isSecured(method)))
42+
.map(ServiceMethodDefinition::fromMethod)
4843
.collect(Collectors.toList());
4944
return new ServiceRegistration(namespace, serviceTags, actions);
5045
})

services-api/src/main/java/io/scalecube/services/annotations/AfterConstruct.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
/**
1111
* This annotation is used to mark the method which will be executed after constructing of service
1212
* and dependency injection is done. <br>
13-
* Scalecube services doesn't support {@link javax.annotation.PostConstruct} since Java API
13+
* NOTE: scalecube services doesn't support {@code javax.annotation.PostConstruct} since Java API
1414
* Specification for it has strict limitation for annotated method.
1515
*/
1616
@Documented

services-api/src/main/java/io/scalecube/services/annotations/BeforeDestroy.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
import java.lang.annotation.Target;
99

1010
/**
11-
* This annotation is used to mark the method which will be executed before shutdown of service <br>
12-
* Scalecube services doesn't support {@link javax.annotation.PreDestroy} since Java API *
11+
* This annotation is used to mark the method which will be executed before shutdown of service.
12+
* <br>
13+
* NOTE: scalecube services doesn't support {@code javax.annotation.PreDestroy} since Java API
1314
* Specification for it has strict limitation for annotated method.
1415
*/
1516
@Documented
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.scalecube.services.annotations;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/** Indicates that annotated method is a REST service method. */
9+
@Retention(RetentionPolicy.RUNTIME)
10+
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
11+
public @interface RestMethod {
12+
13+
/**
14+
* Name of the HTTP method. Supported methods: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS,
15+
* TRACE.
16+
*
17+
* @return http method
18+
*/
19+
String value();
20+
}

services-api/src/main/java/io/scalecube/services/annotations/Service.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import java.lang.annotation.RetentionPolicy;
66
import java.lang.annotation.Target;
77

8-
/** Indicates that annotated class is a ScaleCube service object. */
8+
/** Indicates that annotated class is a scalecube service object. */
99
@Retention(RetentionPolicy.RUNTIME)
1010
@Target(ElementType.TYPE)
1111
public @interface Service {

services-api/src/main/java/io/scalecube/services/annotations/ServiceMethod.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55
import java.lang.annotation.RetentionPolicy;
66
import java.lang.annotation.Target;
77

8-
/**
9-
* Indicates that annotated method is a ScaleCube service method.
10-
*/
8+
/** Indicates that annotated method is a scalecube service method. */
119
@Retention(RetentionPolicy.RUNTIME)
1210
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
1311
public @interface ServiceMethod {

services-api/src/main/java/io/scalecube/services/api/ServiceMessage.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import java.util.HashMap;
55
import java.util.Map;
66
import java.util.Objects;
7-
import java.util.Optional;
87
import java.util.StringJoiner;
98

109
public final class ServiceMessage {
@@ -28,6 +27,9 @@ public final class ServiceMessage {
2827
/** Error type header. */
2928
public static final String HEADER_ERROR_TYPE = "errorType";
3029

30+
/** Request method header. */
31+
public static final String HEADER_REQUEST_METHOD = "requestMethod";
32+
3133
/** Null value for error type. */
3234
public static final int NULL_ERROR_TYPE = -1;
3335

@@ -126,7 +128,8 @@ public String dataFormat() {
126128
* @return data format of the data or default one
127129
*/
128130
public String dataFormatOrDefault() {
129-
return Optional.ofNullable(dataFormat()).orElse(DEFAULT_DATA_FORMAT);
131+
final var dataFormat = dataFormat();
132+
return dataFormat != null ? dataFormat : DEFAULT_DATA_FORMAT;
130133
}
131134

132135
/**
@@ -164,7 +167,7 @@ public boolean hasData(Class<?> dataClass) {
164167
/**
165168
* Describes whether the message is an error.
166169
*
167-
* @return <code>true</code> if error, otherwise <code>false</code>.
170+
* @return result
168171
*/
169172
public boolean isError() {
170173
return headers.containsKey(HEADER_ERROR_TYPE);
@@ -187,6 +190,15 @@ public int errorType() {
187190
}
188191
}
189192

193+
/**
194+
* Returns request method header.
195+
*
196+
* @return request method, or null if such header doesn't exist.
197+
*/
198+
public String requestMethod() {
199+
return headers.get(HEADER_REQUEST_METHOD);
200+
}
201+
190202
@Override
191203
public String toString() {
192204
return new StringJoiner(", ", "ServiceMessage" + "[", "]")

services-api/src/main/java/io/scalecube/services/methods/MethodInfo.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public final class MethodInfo {
2121
private final boolean isRequestTypeServiceMessage;
2222
private final boolean isSecured;
2323
private final Scheduler scheduler;
24+
private final String restMethod;
2425

2526
/**
2627
* Create a new service info.
@@ -35,6 +36,7 @@ public final class MethodInfo {
3536
* @param isRequestTypeServiceMessage is request service message
3637
* @param isSecured is method protected by authentication
3738
* @param scheduler scheduler
39+
* @param restMethod restMethod
3840
*/
3941
public MethodInfo(
4042
String serviceName,
@@ -46,7 +48,8 @@ public MethodInfo(
4648
Class<?> requestType,
4749
boolean isRequestTypeServiceMessage,
4850
boolean isSecured,
49-
Scheduler scheduler) {
51+
Scheduler scheduler,
52+
String restMethod) {
5053
this.parameterizedReturnType = parameterizedReturnType;
5154
this.isReturnTypeServiceMessage = isReturnTypeServiceMessage;
5255
this.communicationMode = communicationMode;
@@ -59,6 +62,7 @@ public MethodInfo(
5962
this.isRequestTypeServiceMessage = isRequestTypeServiceMessage;
6063
this.isSecured = isSecured;
6164
this.scheduler = scheduler;
65+
this.restMethod = restMethod;
6266
}
6367

6468
public String serviceName() {
@@ -113,6 +117,10 @@ public Scheduler scheduler() {
113117
return scheduler;
114118
}
115119

120+
public String restMethod() {
121+
return restMethod;
122+
}
123+
116124
@Override
117125
public String toString() {
118126
return new StringJoiner(", ", MethodInfo.class.getSimpleName() + "[", "]")
@@ -128,6 +136,7 @@ public String toString() {
128136
.add("isRequestTypeServiceMessage=" + isRequestTypeServiceMessage)
129137
.add("isSecured=" + isSecured)
130138
.add("scheduler=" + scheduler)
139+
.add("restMethod=" + restMethod)
131140
.toString();
132141
}
133142
}

0 commit comments

Comments
 (0)