securis/src/main/java/net/curisit/securis/db/Application.java
.. .. @@ -6,6 +6,7 @@ 6 6 7 7 import javax.persistence.Column; 8 8 import javax.persistence.Entity; 9 +import javax.persistence.FetchType;9 10 import javax.persistence.GeneratedValue; 10 11 import javax.persistence.Id; 11 12 import javax.persistence.NamedQueries; .. .. @@ -43,7 +44,7 @@ 43 44 44 45 @JsonIgnore 45 46 // We don't include the referenced entities to limit the size of each row at the listing 46 - @OneToMany(mappedBy = "application")47 + @OneToMany(fetch = FetchType.LAZY, mappedBy = "application")47 48 private Set<LicenseType> licenseTypes; 48 49 49 50 public int getId() { securis/src/main/java/net/curisit/securis/db/Organization.java
.. .. @@ -9,6 +9,7 @@ 9 9 import javax.persistence.CascadeType; 10 10 import javax.persistence.Column; 11 11 import javax.persistence.Entity; 12 +import javax.persistence.FetchType;12 13 import javax.persistence.GeneratedValue; 13 14 import javax.persistence.Id; 14 15 import javax.persistence.JoinColumn; .. .. @@ -36,7 +37,8 @@ 36 37 @Entity 37 38 @Table(name = "organization") 38 39 @NamedQueries( 39 - { @NamedQuery(name = "list-organizations", query = "SELECT o FROM Organization o"), @NamedQuery(name = "find-children-org", query = "SELECT o FROM Organization o where o.parentOrganization = :parentOrganization") })40 + { @NamedQuery(name = "list-organizations", query = "SELECT o FROM Organization o"), @NamedQuery(name = "list-organizations-by-ids", query = "SELECT o FROM Organization o where id in :list_ids"),41 + @NamedQuery(name = "find-children-org", query = "SELECT o FROM Organization o where o.parentOrganization = :parentOrganization") })40 42 public class Organization implements Serializable { 41 43 42 44 @SuppressWarnings("unused") .. .. @@ -73,7 +75,7 @@ 73 75 74 76 @JsonIgnore 75 77 // We don't include the users to limit the size of each row a the listing 76 - @OneToMany(mappedBy = "parentOrganization")78 + @OneToMany(fetch = FetchType.LAZY, mappedBy = "parentOrganization")77 79 private Set<Organization> childOrganizations; 78 80 79 81 public int getId() { securis/src/main/java/net/curisit/securis/db/User.java
.. .. @@ -181,10 +181,10 @@ 181 181 } 182 182 183 183 @JsonProperty("organizations_ids") 184 - public List<Integer> getOrgsIds() {184 + public Set<Integer> getOrgsIds() {185 185 if (organizations == null) 186 186 return null; 187 - List<Integer> ids = new ArrayList<>();187 + Set<Integer> ids = new HashSet<>();188 188 for (Organization org : organizations) { 189 189 ids.add(org.getId()); 190 190 } securis/src/main/java/net/curisit/securis/ioc/RequestsModule.java
.. .. @@ -1,5 +1,6 @@ 1 1 package net.curisit.securis.ioc; 2 2 3 +import net.curisit.securis.security.SecurityInterceptor;3 4 import net.curisit.securis.services.ApiResource; 4 5 import net.curisit.securis.services.ApplicationResource; 5 6 import net.curisit.securis.services.BasicServices; .. .. @@ -8,7 +9,6 @@ 8 9 import net.curisit.securis.services.LicenseTypeResource; 9 10 import net.curisit.securis.services.OrganizationResource; 10 11 import net.curisit.securis.services.PackResource; 11 -import net.curisit.securis.services.SecurityInterceptor;12 12 import net.curisit.securis.services.UserResource; 13 13 14 14 import org.eclipse.jetty.server.Authentication.User; .. .. @@ -24,10 +24,12 @@ 24 24 protected void configure() { 25 25 super.configure(); 26 26 // TODO: Make the bind using reflection dynamically 27 + bind(SecurityInterceptor.class);28 + // bind(SecurityContextWrapper.class).in(RequestScoped.class);29 +27 30 bind(BasicServices.class); 28 31 bind(LicenseServices.class); 29 32 bind(UserResource.class); 30 - bind(SecurityInterceptor.class);31 33 32 34 bind(ApplicationResource.class); 33 35 bind(LicenseTypeResource.class); securis/src/main/java/net/curisit/securis/security/BasicSecurityContext.java
.. .. @@ -0,0 +1,92 @@ 1 +package net.curisit.securis.security;2 +3 +import java.security.Principal;4 +import java.util.Map;5 +import java.util.Set;6 +7 +import javax.ws.rs.core.SecurityContext;8 +9 +import net.curisit.integrity.commons.Utils;10 +import net.curisit.securis.db.User;11 +12 +public class BasicSecurityContext implements SecurityContext {13 +14 + final public static String ROL_ADVANCE = "advance";15 + final public static String ROL_ADMIN = "admin";16 +17 + final static Map<String, Integer> ROLES = Utils.<String, Integer> createMap(ROL_ADVANCE, User.Rol.ADVANCE, ROL_ADMIN, User.Rol.ADMIN);18 +19 + Principal user = null;20 + int roles = 0;21 + boolean secure = false;22 + Set<Integer> organizationsIds = null;23 + double ran = 0;24 +25 + public BasicSecurityContext(String username, int roles, boolean secure) {26 + user = new UserPrincipal(username);27 + this.roles = roles;28 + this.secure = secure;29 + ran = Math.random();30 + }31 +32 + @Override33 + public Principal getUserPrincipal() {34 + return user;35 + }36 +37 + @Override38 + public boolean isUserInRole(String role) {39 + Integer introle = ROLES.get(role);40 + return introle != null && (introle & roles) != 0;41 + }42 +43 + @Override44 + public boolean isSecure() {45 + return secure;46 + }47 +48 + @Override49 + public String getAuthenticationScheme() {50 + return null;51 + }52 +53 + @Override54 + public String toString() {55 +56 + return String.format("SecurityContextWrapper(%f) %s", ran, user);57 + }58 +59 + public void setOrganizationsIds(Set<Integer> orgs) {60 + this.organizationsIds = orgs;61 + }62 +63 + public Set<Integer> getOrganizationsIds() {64 + return this.organizationsIds;65 + }66 +67 + private class UserPrincipal implements Principal {68 +69 + final String name;70 +71 + public UserPrincipal(String name) {72 + this.name = name;73 + }74 +75 + @Override76 + public String getName() {77 + return this.name;78 + }79 +80 + @Override81 + public String toString() {82 + return String.format("[%s]", name);83 + }84 +85 + }86 +87 + public boolean isOrgAccesible(Integer orgid) {88 + if (organizationsIds == null || orgid == null)89 + return false;90 + return organizationsIds.contains(orgid);91 + }92 +}securis/src/main/java/net/curisit/securis/services/Securable.javasimilarity index 93%rename from securis/src/main/java/net/curisit/securis/services/Securable.javarename to securis/src/main/java/net/curisit/securis/security/Securable.java
.. .. @@ -1,4 +1,4 @@ 1 -package net.curisit.securis.services;1 +package net.curisit.securis.security;2 2 3 3 import java.lang.annotation.ElementType; 4 4 import java.lang.annotation.Retention; securis/src/main/java/net/curisit/securis/security/SecurityInterceptor.java
.. .. @@ -0,0 +1,144 @@ 1 +package net.curisit.securis.security;2 +3 +import java.io.IOException;4 +import java.lang.reflect.Method;5 +import java.util.List;6 +import java.util.Set;7 +8 +import javax.annotation.Priority;9 +import javax.inject.Inject;10 +import javax.persistence.EntityManager;11 +import javax.servlet.http.HttpServletRequest;12 +import javax.ws.rs.Priorities;13 +import javax.ws.rs.WebApplicationException;14 +import javax.ws.rs.container.ContainerRequestContext;15 +import javax.ws.rs.core.Context;16 +import javax.ws.rs.core.Response;17 +import javax.ws.rs.core.Response.Status;18 +import javax.ws.rs.ext.Provider;19 +20 +import net.curisit.securis.db.User;21 +import net.curisit.securis.utils.CacheTTL;22 +import net.curisit.securis.utils.TokenHelper;23 +24 +import org.jboss.resteasy.core.Dispatcher;25 +import org.jboss.resteasy.core.ResourceMethodInvoker;26 +import org.jboss.resteasy.core.ServerResponse;27 +import org.jboss.resteasy.spi.Failure;28 +import org.jboss.resteasy.spi.HttpRequest;29 +import org.jboss.resteasy.spi.ResteasyProviderFactory;30 +import org.slf4j.Logger;31 +import org.slf4j.LoggerFactory;32 +33 +//@Provider34 +//@Priority(Priorities.AUTHENTICATION)35 +//public class SecurityInterceptor implements javax.ws.rs.container.ContainerRequestFilter {36 +37 +@Provider38 +// @ServerInterceptor39 +// @Precedence("SECURITY")40 +// public class SecurityInterceptor implements PreProcessInterceptor {41 +// @PreMatching42 +@Priority(Priorities.AUTHENTICATION)43 +public class SecurityInterceptor implements javax.ws.rs.container.ContainerRequestFilter {44 + private static final Logger log = LoggerFactory.getLogger(SecurityInterceptor.class);45 +46 + @Inject47 + private TokenHelper tokenHelper;48 +49 + @Context50 + private HttpServletRequest servletRequest;51 +52 + @Inject53 + CacheTTL cache;54 +55 + @Context56 + Dispatcher dispatcher;57 +58 + @Inject59 + com.google.inject.Provider<EntityManager> emProvider;60 +61 + public void filter(ContainerRequestContext containerRequestContext) throws IOException {62 + // log.info("scw {}, {}", scw, scw.getClass());63 + log.info("MACHED res: {}", containerRequestContext.getUriInfo().getMatchedResources());64 + // dispatcher.getDefaultContextObjects().remove(SecurityContextWrapper.class);65 + ResourceMethodInvoker methodInvoker = (ResourceMethodInvoker) containerRequestContext.getProperty("org.jboss.resteasy.core.ResourceMethodInvoker");66 + Method method = methodInvoker.getMethod();67 +68 + if (!method.isAnnotationPresent(Securable.class))69 + return;70 + String token = servletRequest.getHeader(TokenHelper.TOKEN_HEADER_PÀRAM);71 + if (token == null || !tokenHelper.isTokenValid(token)) {72 + log.info("Access denied to '{}', Token not valid.", servletRequest.getPathInfo());73 + containerRequestContext.abortWith(Response.status(Status.UNAUTHORIZED).build());74 + } else {75 + Securable sec = method.getAnnotation(Securable.class);76 +77 + // If roles == 0 we only need to validate the token78 + String username = tokenHelper.extractUserFromToken(token);79 + int userRoles = getUserRoles(username);80 + // if (sec.roles() != 0) {81 + // if ((sec.roles() & userRoles) == 0) {82 + // log.info("User {} has no necessary role to access url: {}", username, servletRequest.getPathInfo());83 + // containerRequestContext.abortWith(Response.status(Status.UNAUTHORIZED).build());84 + // }85 + // }86 + Set<Integer> orgs = getUserOrganizations(username);87 +88 + BasicSecurityContext scw = new BasicSecurityContext(username, userRoles, servletRequest.isSecure());89 + scw.setOrganizationsIds(orgs);90 + containerRequestContext.setSecurityContext(scw);91 + // Next line provide injection in resource methods92 + log.info("TEST context {}", ResteasyProviderFactory.getContextData(BasicSecurityContext.class));93 + ResteasyProviderFactory.pushContext(BasicSecurityContext.class, scw);94 + // log.info("{}", dispatcher.getDefaultContextObjects());95 + // dispatcher.getDefaultContextObjects().put(SecurityContextWrapper.class, secContext);96 + log.info("Added custom SecurityContext for user {}", username);97 + }98 + }99 +100 + private Set<Integer> getUserOrganizations(String username) {101 + @SuppressWarnings("unchecked")102 + Set<Integer> userOrgs = cache.get("orgs_" + username, Set.class);103 + if (userOrgs == null) {104 + // Theorically this shouldn't be never null, but just in case...105 + EntityManager em = emProvider.get();106 + User user = em.find(User.class, username);107 + if (user != null) {108 + userOrgs = user.getAllOrgsIds();109 + // We store user orgs in cache only for one hour110 + cache.set("orgs_" + username, userOrgs, 3600);111 + }112 + }113 +114 + return userOrgs;115 + }116 +117 + private int getUserRoles(String username) {118 + if (username == null)119 + return 0;120 + Integer userRoles = cache.get("roles_" + username, Integer.class);121 + if (userRoles == null) {122 + EntityManager em = emProvider.get();123 + User user = em.find(User.class, username);124 + if (user != null) {125 + userRoles = 0;126 + List<Integer> roles = user.getRoles();127 + for (Integer rol : roles) {128 + userRoles += rol;129 + }130 + // We store user roles in cache only for one hour131 + cache.set("roles_" + username, userRoles, 3600);132 + cache.set("orgs_" + username, user.getOrgsIds(), 3600);133 + }134 + }135 + return userRoles == null ? 0 : userRoles.intValue();136 + }137 +138 + // @Override139 + public ServerResponse preProcess(HttpRequest request, ResourceMethodInvoker method) throws Failure, WebApplicationException {140 + // TODO Auto-generated method stub141 + return null;142 + }143 +144 +}securis/src/main/java/net/curisit/securis/services/BasicServices.java
.. .. @@ -21,6 +21,7 @@ 21 21 import javax.ws.rs.core.UriBuilder; 22 22 23 23 import net.curisit.integrity.commons.Utils; 24 +import net.curisit.securis.security.Securable;24 25 import net.curisit.securis.utils.TokenHelper; 25 26 26 27 import org.slf4j.Logger; securis/src/main/java/net/curisit/securis/services/OrganizationResource.java
.. .. @@ -4,6 +4,7 @@ 4 4 import java.util.Date; 5 5 import java.util.List; 6 6 7 +import javax.annotation.security.RolesAllowed;7 8 import javax.inject.Inject; 8 9 import javax.inject.Provider; 9 10 import javax.persistence.EntityManager; .. .. @@ -27,8 +28,11 @@ 27 28 import net.curisit.securis.SecurisErrorHandler; 28 29 import net.curisit.securis.db.Organization; 29 30 import net.curisit.securis.db.User; 31 +import net.curisit.securis.security.BasicSecurityContext;32 +import net.curisit.securis.security.Securable;30 33 import net.curisit.securis.utils.TokenHelper; 31 34 35 +import org.jboss.resteasy.spi.ResteasyProviderFactory;32 36 import org.slf4j.Logger; 33 37 import org.slf4j.LoggerFactory; 34 38 .. .. @@ -45,10 +49,7 @@ 45 49 private static final Logger log = LoggerFactory.getLogger(OrganizationResource.class); 46 50 47 51 @Inject 48 - TokenHelper tokenHelper;49 -50 - @Inject51 - Provider<EntityManager> emProvider;52 + private Provider<EntityManager> emProvider;52 53 53 54 public OrganizationResource() { 54 55 } .. .. @@ -61,11 +62,30 @@ 61 62 @Path("/") 62 63 @Produces( 63 64 { MediaType.APPLICATION_JSON }) 64 - public Response index() {65 + @Securable66 + // @RolesAllowed(SecurityContextWrapper.ROL_ADVANCE)67 + public Response index(@Context BasicSecurityContext bsc) {65 68 log.info("Getting organizations list "); 66 69 70 + // log.info("User orgs: {}", request.getAttribute("oser_orgs"));71 + BasicSecurityContext bsc2 = ResteasyProviderFactory.getContextData(BasicSecurityContext.class);72 + log.info("bsc: {}", bsc);73 + log.info("bsc2: {}", bsc2);74 + // log.info("securityContext: {}", scw);75 + log.info("securityContext ROL_ADMIN?: {}", bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN));67 76 EntityManager em = emProvider.get(); 68 - TypedQuery<Organization> q = em.createNamedQuery("list-organizations", Organization.class);77 + TypedQuery<Organization> q;78 + if (bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) {79 + log.info("GEtting all orgs for user: " + bsc.getUserPrincipal());80 + q = em.createNamedQuery("list-organizations", Organization.class);81 + } else {82 + q = em.createNamedQuery("list-organizations", Organization.class);83 + // if (securityContext.getOrganizationsIds() == null)84 + // Response.ok().build();85 + // log.info("Getting only {} orgs for user: {}", securityContext.getOrganizationsIds(), securityContext.getUserPrincipal());86 + // q = em.createNamedQuery("list-organizations-by-ids", Organization.class);87 + // q.setParameter("list_ids", securityContext.getOrganizationsIds());88 + }69 89 70 90 List<Organization> list = q.getResultList(); 71 91 .. .. @@ -80,12 +100,17 @@ 80 100 @Path("/{orgid}") 81 101 @Produces( 82 102 { MediaType.APPLICATION_JSON }) 103 + @Securable83 104 public Response get(@PathParam("orgid") String orgid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { 84 105 log.info("Getting organization data for id: {}: ", orgid); 85 106 if (orgid == null || orgid.equals("")) { 86 107 log.error("Organization ID is mandatory"); 87 108 return Response.status(Status.NOT_FOUND).build(); 88 109 } 110 + // if (!securityContext.isOrgAccesible(Integer.parseInt(orgid))) {111 + // log.error("Organization with id {} not accessible for user: {}", orgid, securityContext.getUserPrincipal());112 + // return Response.status(Status.UNAUTHORIZED).build();113 + // }89 114 90 115 EntityManager em = emProvider.get(); 91 116 Organization lt = em.find(Organization.class, Integer.parseInt(orgid)); .. .. @@ -111,6 +136,8 @@ 111 136 @Produces( 112 137 { MediaType.APPLICATION_JSON }) 113 138 @Transactional 139 + @Securable140 + @RolesAllowed(BasicSecurityContext.ROL_ADMIN)114 141 public Response create(Organization org, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { 115 142 log.info("Creating new organization"); 116 143 EntityManager em = emProvider.get(); .. .. @@ -151,6 +178,8 @@ 151 178 @Consumes(MediaType.APPLICATION_JSON) 152 179 @Produces( 153 180 { MediaType.APPLICATION_JSON }) 181 + @Securable182 + @RolesAllowed(BasicSecurityContext.ROL_ADMIN)154 183 public Response modify(Organization org, @PathParam("orgid") String orgid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { 155 184 log.info("Modifying organization with id: {}", orgid); 156 185 EntityManager em = emProvider.get(); .. .. @@ -201,6 +230,8 @@ 201 230 @Transactional 202 231 @Produces( 203 232 { MediaType.APPLICATION_JSON }) 233 + @Securable234 + @RolesAllowed(BasicSecurityContext.ROL_ADMIN)204 235 public Response delete(@PathParam("orgid") String orgid, @Context HttpServletRequest request) { 205 236 log.info("Deleting organization with id: {}", orgid); 206 237 EntityManager em = emProvider.get(); securis/src/main/java/net/curisit/securis/services/SecurityInterceptor.javadeleted file mode 100644
.. .. @@ -1,108 +0,0 @@ 1 -package net.curisit.securis.services;2 -3 -import java.io.IOException;4 -import java.lang.reflect.Method;5 -import java.util.List;6 -import java.util.Set;7 -8 -import javax.inject.Inject;9 -import javax.persistence.EntityManager;10 -import javax.servlet.http.HttpServletRequest;11 -import javax.ws.rs.container.ContainerRequestContext;12 -import javax.ws.rs.core.Context;13 -import javax.ws.rs.core.Response;14 -import javax.ws.rs.core.Response.Status;15 -import javax.ws.rs.ext.Provider;16 -17 -import net.curisit.securis.db.User;18 -import net.curisit.securis.utils.CacheTTL;19 -import net.curisit.securis.utils.TokenHelper;20 -21 -import org.jboss.resteasy.core.ResourceMethodInvoker;22 -import org.slf4j.Logger;23 -import org.slf4j.LoggerFactory;24 -25 -@Provider26 -public class SecurityInterceptor implements javax.ws.rs.container.ContainerRequestFilter {27 -28 - private static final Logger log = LoggerFactory.getLogger(SecurityInterceptor.class);29 -30 - @Inject31 - private TokenHelper tokenHelper;32 -33 - @Context34 - private HttpServletRequest servletRequest;35 -36 - @Inject37 - CacheTTL cache;38 -39 - @Inject40 - com.google.inject.Provider<EntityManager> emProvider;41 -42 - @Override43 - public void filter(ContainerRequestContext containerRequestContext) throws IOException {44 - ResourceMethodInvoker methodInvoker = (ResourceMethodInvoker) containerRequestContext.getProperty("org.jboss.resteasy.core.ResourceMethodInvoker");45 - Method method = methodInvoker.getMethod();46 -47 - if (!method.isAnnotationPresent(Securable.class))48 - return;49 - String token = servletRequest.getHeader(TokenHelper.TOKEN_HEADER_PÀRAM);50 - if (token == null || !tokenHelper.isTokenValid(token)) {51 - log.info("Access denied to '{}', Token not valid.", servletRequest.getPathInfo());52 - containerRequestContext.abortWith(Response.status(Status.UNAUTHORIZED).build());53 - } else {54 - Securable sec = method.getAnnotation(Securable.class);55 -56 - // If roles == 0 we only need to validate the token57 - if (sec.roles() != 0) {58 - String username = tokenHelper.extractUserFromToken(token);59 - int userRoles = getUserRoles(username);60 - if ((sec.roles() & userRoles) == 0) {61 - log.info("User {} has no necessary role to access url: {}", username, servletRequest.getPathInfo());62 - containerRequestContext.abortWith(Response.status(Status.UNAUTHORIZED).build());63 - }64 - Set<Integer> orgs = getUserOrganizations(username);65 - servletRequest.setAttribute("user_orgs", orgs);66 - }67 - }68 - }69 -70 - private Set<Integer> getUserOrganizations(String username) {71 - @SuppressWarnings("unchecked")72 - Set<Integer> userOrgs = cache.get("orgs_" + username, Set.class);73 - if (userOrgs == null) {74 - // Theorically this shouldn't be never null, but just in case...75 - EntityManager em = emProvider.get();76 - User user = em.find(User.class, username);77 - if (user != null) {78 - userOrgs = user.getAllOrgsIds();79 - // We store user orgs in cache only for one hour80 - cache.set("orgs_" + username, userOrgs, 3600);81 - }82 - }83 -84 - return userOrgs;85 - }86 -87 - private int getUserRoles(String username) {88 - if (username == null)89 - return 0;90 - Integer userRoles = cache.get("roles_" + username, Integer.class);91 - if (userRoles == null) {92 - EntityManager em = emProvider.get();93 - User user = em.find(User.class, username);94 - if (user != null) {95 - userRoles = 0;96 - List<Integer> roles = user.getRoles();97 - for (Integer rol : roles) {98 - userRoles += rol;99 - }100 - // We store user roles in cache only for one hour101 - cache.set("roles_" + username, userRoles, 3600);102 - cache.set("orgs_" + username, user.getOrgsIds(), 3600);103 - }104 - }105 - return userRoles == null ? 0 : userRoles.intValue();106 - }107 -108 -}securis/src/main/java/net/curisit/securis/services/UserResource.java
.. .. @@ -115,7 +115,7 @@ 115 115 } 116 116 117 117 Set<Organization> orgs = null; 118 - List<Integer> orgsIds = user.getOrgsIds();118 + Set<Integer> orgsIds = user.getOrgsIds();119 119 if (orgsIds != null && orgsIds.size() > 0) { 120 120 orgs = new HashSet<>(); 121 121 for (Integer orgId : orgsIds) { .. .. @@ -154,7 +154,7 @@ 154 154 } 155 155 156 156 Set<Organization> orgs = null; 157 - List<Integer> orgsIds = user.getOrgsIds();157 + Set<Integer> orgsIds = user.getOrgsIds();158 158 if (orgsIds != null && orgsIds.size() > 0) { 159 159 orgs = new HashSet<>(); 160 160 for (Integer orgId : orgsIds) { securis/src/main/java/net/curisit/securis/utils/CacheTTL.java
.. .. @@ -1,7 +1,9 @@ 1 1 package net.curisit.securis.utils; 2 2 3 +import java.util.ArrayList;3 4 import java.util.Date; 4 -import java.util.Hashtable;5 +import java.util.HashMap;6 +import java.util.List;5 7 import java.util.Map; 6 8 7 9 import javax.inject.Inject; .. .. @@ -25,7 +27,7 @@ 25 27 */ 26 28 private static int DEFAULT_CACHE_DURATION = 24 * 60 * 60; 27 29 28 - private Map<String, CachedObject> data = new Hashtable<>();30 + private Map<String, CachedObject> data = new HashMap<>();29 31 30 32 private Thread cleaningThread = null; 31 33 .. .. @@ -46,12 +48,17 @@ 46 48 } 47 49 // log.info("Cheking expired objects " + new Date()); 48 50 Date now = new Date(); 51 + List<String> keysToRemove = new ArrayList<>();49 52 for (String key : CacheTTL.this.data.keySet()) { 50 53 CachedObject co = CacheTTL.this.data.get(key); 51 54 if (now.after(co.getExpireAt())) { 52 - CacheTTL.this.data.remove(key);55 + keysToRemove.add(key);53 56 } 54 57 } 58 + for (String key : keysToRemove) {59 + // If we try to remove directly in the previous loop an exception is thrown java.util.ConcurrentModificationException60 + CacheTTL.this.data.remove(key);61 + }55 62 } 56 63 } 57 64 });