com.github.antoniomacri.reactivegwt.proxy.RpcPolicyFinder Maven / Gradle / Ivy
The newest version!
/*
* Copyright www.gdevelop.com.
* Copyright Antonio Macrì
*
* 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
*
* 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 com.github.antoniomacri.reactivegwt.proxy;
import com.google.gwt.user.client.rpc.InvocationException;
import com.google.gwt.user.server.rpc.SerializationPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class RpcPolicyFinder {
private static final Logger log = LoggerFactory.getLogger(RpcPolicyFinder.class);
private static final String GWT_PRC_POLICY_FILE_EXT = ".gwt.rpc";
private static final Pattern PERMUTATION_NAME_PATTERN = Pattern.compile("'([A-Z0-9]){32}'");
private static final Pattern CACHE_JS_FILE_PATTERN = Pattern.compile("([A-Z0-9]){32}\\.cache\\.js");
private static final Pattern POLICY_NAME_DOUBLE_QUOTES_PATTERN = Pattern.compile("\"([A-Z0-9]){32}\"");
private static final Pattern POLICY_NAME_SINGLE_QUOTES_PATTERN = Pattern.compile("'([A-Z0-9]){32}'");
private static final ExecutorService DEFAULT_EXECUTOR = Executors.newSingleThreadExecutor();
private final String moduleBaseURL;
private final Map policyNameByService = new ConcurrentHashMap<>();
private final Map policyByName = new ConcurrentHashMap<>();
public RpcPolicyFinder(String moduleBaseURL) {
this.moduleBaseURL = moduleBaseURL.trim();
}
public String getOrFetchPolicyName(String serviceName) {
try {
return getOrFetchPolicyNameAsync(serviceName, DEFAULT_EXECUTOR).toCompletableFuture().get();
} catch (InterruptedException | ExecutionException e) {
throw new InvocationException("Error while fetching serialization policy", e);
}
}
public CompletionStage getOrFetchPolicyNameAsync(String serviceName, Executor executor) {
String policyName = policyNameByService.get(serviceName);
if (policyName != null) {
return CompletableFuture.completedFuture(policyName);
} else {
return fetchPolicyNameAsync(serviceName, executor);
}
}
public CompletionStage fetchPolicyNameAsync(String serviceName, Executor executor) {
return fetchSerializationPoliciesAsync(executor).thenApply(ignored -> {
String newPolicyName = policyNameByService.get(serviceName);
return newPolicyName;
});
}
public SerializationPolicy getSerializationPolicy(String policyName) {
return policyByName.get(policyName);
}
/**
* Map from ServiceInterface class name to Serialization Policy name.
*/
private CompletionStage fetchSerializationPoliciesAsync(Executor executor) {
Map newPolicyNameByService = new HashMap<>();
Map newPolicyByName = new HashMap<>();
log.info("Fetching serialization policies...");
HttpClient httpClient = HttpClient.newBuilder()
.executor(executor)
.version(HttpClient.Version.HTTP_2)
.build();
String[] urlparts = moduleBaseURL.split("/");
String moduleNoCacheJs = urlparts[urlparts.length - 1] + ".nocache.js";
return getResposeTextAsync(moduleBaseURL + moduleNoCacheJs, httpClient, executor).thenComposeAsync(noCacheJsFileContent -> {
Matcher matcher = PERMUTATION_NAME_PATTERN.matcher(noCacheJsFileContent);
if (matcher.find()) {
boolean xsiFrameLinker = noCacheJsFileContent.contains(".cache.js");
if (xsiFrameLinker) {
log.debug("Searching for policies generated by XSIFrame linker");
String permutationFile = matcher.group().replace("'", "") + ".cache.js";
return getResposeTextAsync(moduleBaseURL + permutationFile, httpClient, executor).thenApplyAsync(responseText -> {
return POLICY_NAME_DOUBLE_QUOTES_PATTERN.matcher(responseText).results()
.map(mr -> mr.group().replace("\"", ""))
.collect(Collectors.toSet());
}, executor);
} else {
log.debug("Searching for policies generated by standard linker");
String permutationFile = matcher.group().replace("'", "") + ".cache.html";
return getResposeTextAsync(moduleBaseURL + permutationFile, httpClient, executor).thenApplyAsync(responseText -> {
return POLICY_NAME_SINGLE_QUOTES_PATTERN.matcher(responseText).results()
.skip(1 /* the permutation name */)
.map(mr -> mr.group().replace("'", ""))
.collect(Collectors.toSet());
}, executor);
}
} else {
return CompletableFuture.completedFuture(Set.of());
}
}, executor).thenComposeAsync(policyNames -> {
if (policyNames == null || policyNames.isEmpty()) {
// Examine the compilation-mappings.txt file that is generated by GWT, in the event
// (such as in 2.7.0) that serialization policies are no longer in the nocache.js file
log.info("No RemoteService fetched from server using JS/HTML fetcher, using JS fetcher...");
return getResposeTextAsync(moduleBaseURL + "compilation-mappings.txt", httpClient, executor).thenComposeAsync(compilationMappings -> {
Matcher matcher = CACHE_JS_FILE_PATTERN.matcher(compilationMappings);
if (matcher.find()) {
String browserSpec = matcher.group();
return getResposeTextAsync(moduleBaseURL + browserSpec, httpClient, executor).thenApplyAsync(cacheJs -> {
return POLICY_NAME_SINGLE_QUOTES_PATTERN.matcher(cacheJs).results()
.skip(1 /* the permutation name */)
.map(mr -> mr.group().replace("'", ""))
.collect(Collectors.toSet());
}, executor);
} else {
return CompletableFuture.completedFuture(Set.of());
}
}, executor);
} else {
return CompletableFuture.completedFuture(policyNames);
}
}, executor).thenComposeAsync(policyNames -> {
CompletableFuture>[] completableFutures = new CompletableFuture[policyNames.size()];
int i = 0;
for (String policyName : policyNames) {
String policyUrl = moduleBaseURL + policyName + GWT_PRC_POLICY_FILE_EXT;
completableFutures[i] = getResposeTextAsync(policyUrl, httpClient, executor).thenAcceptAsync(policyContent -> {
SerializationPolicy serializationPolicy;
List notFoundExceptions = new ArrayList<>();
try {
serializationPolicy = SerializationPolicyLoader.load(new StringReader(policyContent), notFoundExceptions);
} catch (IOException | ParseException e) {
log.error("Error while loading serialization policy" + policyName, e);
return;
}
if (!notFoundExceptions.isEmpty()) {
log.error("Classes not found while loading serialization policy={} exceptions={}", policyName, notFoundExceptions);
}
AtomicInteger addedServices = new AtomicInteger();
policyContent.lines().forEach(line -> {
int pos = line.indexOf(", false, false, false, false, _, ");
if (pos > 0) {
String serviceName = line.substring(0, pos);
newPolicyNameByService.put(serviceName, policyName);
newPolicyNameByService.put(serviceName + "Async", policyName);
addedServices.incrementAndGet();
log.debug("Created mapping of serviceName={}/Async to policy={}", serviceName, policyName);
}
});
if (addedServices.get() > 0) {
newPolicyByName.put(policyName, serializationPolicy);
log.debug("Added policy from url={} with {} service(s)", policyUrl, addedServices.get());
}
}, executor);
i++;
}
return CompletableFuture.allOf(completableFutures);
}, executor).thenAcceptAsync(ignored -> {
if (newPolicyNameByService.isEmpty()) {
log.info("No RemoteService fetched from server");
} else {
log.info("Found {} RemoteService(s) from {} policies: {}",
newPolicyNameByService.size(), newPolicyByName.size(), String.join(", ", newPolicyNameByService.keySet()));
}
policyNameByService.putAll(newPolicyNameByService);
policyByName.putAll(newPolicyByName);
}, executor);
}
private CompletableFuture getResposeTextAsync(String url, HttpClient httpClient, Executor executor) {
URI uri = URI.create(url);
HttpRequest request = HttpRequest.newBuilder().GET().uri(uri).build();
log.debug("Getting resource at url=%s".formatted(url));
return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenApplyAsync(response -> {
String responseText = response.body();
return responseText;
}, executor);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy