Skip to content

Commit

Permalink
[JIRA GEOS-8539] [GeoServer REST Role Service] - Implement a configur…
Browse files Browse the repository at this point in the history
…able internal cache to avoid making calls to the REST endpoint on the short period
  • Loading branch information
Alessio Fabiani committed Jan 22, 2018
1 parent 1e1ee06 commit 0a3f0ee
Show file tree
Hide file tree
Showing 7 changed files with 428 additions and 281 deletions.
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
Expand All @@ -19,6 +21,9 @@
import java.util.Set; import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;


Expand All @@ -33,9 +38,13 @@
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;


import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException; import com.jayway.jsonpath.PathNotFoundException;


import net.minidev.json.JSONArray;

/** /**
* @author Alessio Fabiani, GeoSolutions S.A.S. * @author Alessio Fabiani, GeoSolutions S.A.S.
* *
Expand All @@ -48,6 +57,8 @@ public class GeoServerRestRoleService extends AbstractGeoServerSecurityService


static final Map<String, String> emptyMap = Collections.emptyMap(); static final Map<String, String> emptyMap = Collections.emptyMap();


static Cache<String, String> cachedResponses;

/** /**
* Sets a specified timeout value, in milliseconds, to be used when opening a * Sets a specified timeout value, in milliseconds, to be used when opening a
* communications link to the resource referenced by this URLConnection. * communications link to the resource referenced by this URLConnection.
Expand Down Expand Up @@ -117,6 +128,12 @@ public void initializeFromConfig(SecurityNamedServiceConfig config) throws IOExc
if (!isEmpty(restRoleServiceConfig.getGroupAdminRoleName())) { if (!isEmpty(restRoleServiceConfig.getGroupAdminRoleName())) {
this.groupAdminGroup = restRoleServiceConfig.getGroupAdminRoleName(); this.groupAdminGroup = restRoleServiceConfig.getGroupAdminRoleName();
} }

cachedResponses = CacheBuilder.newBuilder()
.concurrencyLevel(restRoleServiceConfig.getCacheConcurrencyLevel())
.maximumSize(restRoleServiceConfig.getCacheMaximumSize())
.expireAfterWrite(restRoleServiceConfig.getCacheExpirationTime(), TimeUnit.MILLISECONDS)
.build(); // look Ma, no CacheLoader
} }


/** /**
Expand Down Expand Up @@ -173,22 +190,23 @@ public SortedSet<GeoServerRole> getRolesForUser(String username) throws IOExcept
return (SortedSet<GeoServerRole>) connectToRESTEndpoint( return (SortedSet<GeoServerRole>) connectToRESTEndpoint(
restRoleServiceConfig.getBaseUrl(), restRoleServiceConfig.getBaseUrl(),
restRoleServiceConfig.getUsersRESTEndpoint() + "/" + username, restRoleServiceConfig.getUsersRESTEndpoint() + "/" + username,
restRoleServiceConfig.getUsersJSONPath(), restRoleServiceConfig.getUsersJSONPath().replace("${username}", username),
new RestEndpointConnectionCallback() { new RestEndpointConnectionCallback() {


@Override @Override
public Object executeWithContext(String json) throws Exception { public Object executeWithContext(String json) throws Exception {
try { try {
List<String> rolesString = JsonPath.read(json, List<Object> rolesString = JsonPath.read(json,
restRoleServiceConfig.getUsersJSONPath()); restRoleServiceConfig.getUsersJSONPath().replace("${username}", username));


for (String role : rolesString) { for (Object roleObj : rolesString) {
if (role.startsWith(rolePrefix)) { if (roleObj instanceof String) {
// remove standard role prefix populateRoles((String) roleObj, roles);
role = role.substring(rolePrefix.length()); } else if (roleObj instanceof JSONArray) {
for(Object role : ((JSONArray) roleObj)) {
populateRoles((String) role, roles);
}
} }

roles.add(createRoleObject(role));
} }
} catch (PathNotFoundException ex) { } catch (PathNotFoundException ex) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, null, ex); Logger.getLogger(getClass().getName()).log(Level.WARNING, null, ex);
Expand All @@ -204,6 +222,16 @@ public Object executeWithContext(String json) throws Exception {


return finalRoles; return finalRoles;
} }

private void populateRoles(String role, final SortedSet<GeoServerRole> roles)
throws IOException {
if (role.startsWith(rolePrefix)) {
// remove standard role prefix
role = role.substring(rolePrefix.length());
}

roles.add(createRoleObject(role));
}
}); });
} catch (Exception ex) { } catch (Exception ex) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, null, ex); Logger.getLogger(getClass().getName()).log(Level.WARNING, null, ex);
Expand Down Expand Up @@ -448,63 +476,83 @@ protected Object connectToRESTEndpoint(
final String roleRESTEndpoint, final String roleRESTEndpoint,
final String roleJSONPath, final String roleJSONPath,
RestEndpointConnectionCallback callback) throws Exception { RestEndpointConnectionCallback callback) throws Exception {
// HttpURLConnection conn = null; final String restEndPoint = roleRESTBaseURL + roleRESTEndpoint + roleJSONPath;
ClientHttpRequest clientRequest = null; // First search on cache
ClientHttpResponse clientResponse = null; final String hash = getHash(restEndPoint);

try { try {
final URI baseURI = new URI(roleRESTBaseURL); // If the key wasn't in the "easy to compute" group, we need to

// do things the hard way.
URL url = baseURI.resolve(roleRESTEndpoint).toURL(); final String cachedResponse = cachedResponses.get(hash, new Callable<String>() {


/*conn = (HttpURLConnection) url.openConnection(); @Override
conn.setRequestMethod("GET"); public String call() throws Exception {
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Content-length", "0"); LOGGER.fine("GeoServer REST Role Service CACHE MISS for '" + restEndPoint + "'");
conn.setUseCaches(false);
conn.setAllowUserInteraction(false); ClientHttpRequest clientRequest = null;
conn.setConnectTimeout(CONN_TIMEOUT); ClientHttpResponse clientResponse = null;
conn.setReadTimeout(READ_TIMEOUT); try {
conn.connect(); final URI baseURI = new URI(roleRESTBaseURL);
int status = conn.getResponseCode();*/
clientRequest = getRestTemplate().getRequestFactory().createRequest(url.toURI(), HttpMethod.GET); URL url = baseURI.resolve(roleRESTEndpoint).toURL();
clientResponse = clientRequest.execute();
int status = clientResponse.getRawStatusCode(); clientRequest = getRestTemplate().getRequestFactory()

.createRequest(url.toURI(), HttpMethod.GET);
switch (status) { clientResponse = clientRequest.execute();
case 200: int status = clientResponse.getRawStatusCode();
case 201:
BufferedReader br = new BufferedReader( switch (status) {
new InputStreamReader(clientResponse.getBody())); case 200:
StringBuilder sb = new StringBuilder(); case 201:
String line; BufferedReader br = new BufferedReader(
while ((line = br.readLine()) != null) { new InputStreamReader(clientResponse.getBody()));
sb.append(line + "\n"); StringBuilder sb = new StringBuilder();
} String line;
br.close(); while ((line = br.readLine()) != null) {
sb.append(line + "\n");
}
br.close();


String json = sb.toString(); String json = sb.toString();


return callback.executeWithContext(json); return json;
} }
} catch (MalformedURLException ex) { } catch (MalformedURLException ex) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, null, ex); Logger.getLogger(getClass().getName()).log(Level.WARNING, null, ex);
} catch (IOException ex) { } catch (IOException ex) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, null, ex); Logger.getLogger(getClass().getName()).log(Level.WARNING, null, ex);
} catch (URISyntaxException ex) { } catch (URISyntaxException ex) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, null, ex); Logger.getLogger(getClass().getName()).log(Level.WARNING, null, ex);
} finally { } finally {
if (clientResponse != null) { if (clientResponse != null) {
try { try {
clientResponse.close(); clientResponse.close();
} catch (Exception ex) { } catch (Exception ex) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, ex); Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, ex);
}
}
}

return null;
} }
} });
}


return null; return callback.executeWithContext(cachedResponse);
} catch (ExecutionException e) {
LOGGER.log(Level.WARNING, e.getMessage(), e);
return null;
}
} }


private static String getHash(String stringToEncrypt) throws NoSuchAlgorithmException {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(stringToEncrypt.getBytes());
final String encryptedString = new String(messageDigest.digest());

return encryptedString;
}

/** /**
* Callback interface to be used in the REST call methods for performing operations on individually HTTP JSON responses. * Callback interface to be used in the REST call methods for performing operations on individually HTTP JSON responses.
* *
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ public class GeoServerRestRoleServiceConfig extends BaseSecurityNamedServiceConf
/** serialVersionUID */ /** serialVersionUID */
private static final long serialVersionUID = -8380244566532287415L; private static final long serialVersionUID = -8380244566532287415L;


private static final int defaultCacheConcurrencyLevel = 4;

private static final long defaultCacheMaximumSize = 10000;

private static final long defaultCacheExpirationTime = 30000;

private String adminGroup; private String adminGroup;


private String groupAdminGroup; private String groupAdminGroup;
Expand All @@ -33,7 +39,13 @@ public class GeoServerRestRoleServiceConfig extends BaseSecurityNamedServiceConf


private String adminRoleJSONPath = "$.adminRole"; private String adminRoleJSONPath = "$.adminRole";


private String usersJSONPath = "$.users[0].groups"; private String usersJSONPath = "$.users[?(@.username=='${username}')].groups";

private int cacheConcurrencyLevel = defaultCacheConcurrencyLevel;

private long cacheMaximumSize = defaultCacheMaximumSize;

private long cacheExpirationTime = defaultCacheExpirationTime;


@Override @Override
public String getAdminRoleName() { public String getAdminRoleName() {
Expand Down Expand Up @@ -153,4 +165,58 @@ public void setUsersJSONPath(String usersJSONPath) {
this.usersJSONPath = usersJSONPath; this.usersJSONPath = usersJSONPath;
} }


/**
* @return the cacheConcurrencyLevel
*/
public int getCacheConcurrencyLevel() {
if (cacheConcurrencyLevel > 1) {
return cacheConcurrencyLevel;
} else {
return defaultCacheConcurrencyLevel;
}
}

/**
* @param cacheConcurrencyLevel the cacheConcurrencyLevel to set
*/
public void setCacheConcurrencyLevel(int cacheConcurrencyLevel) {
this.cacheConcurrencyLevel = cacheConcurrencyLevel;
}

/**
* @return the cacheMaximumSize
*/
public long getCacheMaximumSize() {
if (cacheMaximumSize > 0) {
return cacheMaximumSize;
} else {
return defaultCacheMaximumSize;
}
}

/**
* @param cacheMaximumSize the cacheMaximumSize to set
*/
public void setCacheMaximumSize(long cacheMaximumSize) {
this.cacheMaximumSize = cacheMaximumSize;
}

/**
* @param cacheExpirationTime the cacheExpirationTime to set
*/
public void setCacheExpirationTime(long cacheExpirationTime) {
this.cacheExpirationTime = cacheExpirationTime;
}

/**
* @return
*/
public long getCacheExpirationTime() {
if (cacheExpirationTime > 0) {
return cacheExpirationTime;
} else {
return defaultCacheExpirationTime;
}
}

} }
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -47,6 +47,22 @@
<label for="usersJSONPath"><wicket:message key="usersJSONPath"></wicket:message></label> <label for="usersJSONPath"><wicket:message key="usersJSONPath"></wicket:message></label>
<input id="usersJSONPath" wicket:id="usersJSONPath" type="text" class="text"></input> <input id="usersJSONPath" wicket:id="usersJSONPath" type="text" class="text"></input>
</li> </li>

<li>
<label for="cacheConcurrencyLevel"><wicket:message key="cacheConcurrencyLevel"></wicket:message></label>
<input id="cacheConcurrencyLevel" wicket:id="cacheConcurrencyLevel" type="text" class="text"></input>
</li>

<li>
<label for="cacheMaximumSize"><wicket:message key="cacheMaximumSize"></wicket:message></label>
<input id="cacheMaximumSize" wicket:id="cacheMaximumSize" type="text" class="text"></input>
</li>

<li>
<label for="cacheExpirationTime"><wicket:message key="cacheExpirationTime"></wicket:message></label>
<input id="cacheExpirationTime" wicket:id="cacheExpirationTime" type="text" class="text"></input>
</li>

</ul> </ul>
</fieldset> </fieldset>
</li> </li>
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public GeoServerRestRoleServicePanel(String id, IModel<GeoServerRestRoleServiceC
add(new TextField<String>("rolesJSONPath").setRequired(true)); add(new TextField<String>("rolesJSONPath").setRequired(true));
add(new TextField<String>("adminRoleJSONPath").setRequired(true)); add(new TextField<String>("adminRoleJSONPath").setRequired(true));
add(new TextField<String>("usersJSONPath").setRequired(true)); add(new TextField<String>("usersJSONPath").setRequired(true));
add(new TextField<Integer>("cacheConcurrencyLevel").setRequired(true));
add(new TextField<Long>("cacheMaximumSize").setRequired(true));
add(new TextField<Long>("cacheExpirationTime").setRequired(true));
} }


} }
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -49,3 +49,6 @@ GeoServerRestRoleServicePanel.usersRESTEndpoint=Users REST Endpoint
GeoServerRestRoleServicePanel.rolesJSONPath=Roles JSON Path GeoServerRestRoleServicePanel.rolesJSONPath=Roles JSON Path
GeoServerRestRoleServicePanel.adminRoleJSONPath=Admin Role JSON Path GeoServerRestRoleServicePanel.adminRoleJSONPath=Admin Role JSON Path
GeoServerRestRoleServicePanel.usersJSONPath=Users JSON Path GeoServerRestRoleServicePanel.usersJSONPath=Users JSON Path
GeoServerRestRoleServicePanel.cacheConcurrencyLevel=REST Rules Cache Concurrency Level
GeoServerRestRoleServicePanel.cacheMaximumSize=REST Rules Cache Maximum Size (# keys)
GeoServerRestRoleServicePanel.cacheExpirationTime=REST Rules Cache Expiration Time (ms)

0 comments on commit 0a3f0ee

Please sign in to comment.