diff --git a/src/main/java/com/knowledgepixels/nanodash/domain/Space.java b/src/main/java/com/knowledgepixels/nanodash/domain/Space.java index d0930435..d33fda2f 100644 --- a/src/main/java/com/knowledgepixels/nanodash/domain/Space.java +++ b/src/main/java/com/knowledgepixels/nanodash/domain/Space.java @@ -84,8 +84,8 @@ private static String getCoreInfoString(ApiResponseEntry resp) { return id + " " + rootNanopubId; } - private boolean dataInitialized = false; - private boolean dataNeedsUpdate = true; + private volatile boolean dataInitialized = false; + private volatile boolean dataNeedsUpdate = true; Space(ApiResponseEntry resp) { super(resp.get("space")); diff --git a/src/main/java/com/knowledgepixels/nanodash/repository/MaintainedResourceRepository.java b/src/main/java/com/knowledgepixels/nanodash/repository/MaintainedResourceRepository.java index e4d667c0..9e5b2090 100644 --- a/src/main/java/com/knowledgepixels/nanodash/repository/MaintainedResourceRepository.java +++ b/src/main/java/com/knowledgepixels/nanodash/repository/MaintainedResourceRepository.java @@ -48,29 +48,33 @@ private MaintainedResourceRepository() { * @param resp The API response containing maintained resource data. */ public synchronized void refresh(ApiResponse resp) { - resourceList = new ArrayList<>(); - resourcesById = new HashMap<>(); - resourcesBySpace = new HashMap<>(); - resourcesByNamespace = new HashMap<>(); + List newResourceList = new ArrayList<>(); + Map newResourcesById = new HashMap<>(); + Map> newResourcesBySpace = new HashMap<>(); + Map newResourcesByNamespace = new HashMap<>(); for (ApiResponseEntry entry : resp.getData()) { Space space = SpaceRepository.get().findById(entry.get("space")); if (space == null) { continue; } MaintainedResource resource = MaintainedResourceFactory.getOrCreate(entry, space); - if (resourcesById.containsKey(resource.getId())) { + if (newResourcesById.containsKey(resource.getId())) { continue; } - resourceList.add(resource); - resourcesById.put(resource.getId(), resource); - resourcesBySpace.computeIfAbsent(space, k -> new ArrayList<>()).add(resource); + newResourceList.add(resource); + newResourcesById.put(resource.getId(), resource); + newResourcesBySpace.computeIfAbsent(space, k -> new ArrayList<>()).add(resource); if (resource.getNamespace() != null) { // TODO Handle conflicts when two resources claim the same namespace: - resourcesByNamespace.put(resource.getNamespace(), resource); + newResourcesByNamespace.put(resource.getNamespace(), resource); } } - MaintainedResourceFactory.removeStale(resourcesById.keySet()); + MaintainedResourceFactory.removeStale(newResourcesById.keySet()); + resourcesById = newResourcesById; + resourcesBySpace = newResourcesBySpace; + resourcesByNamespace = newResourcesByNamespace; loaded = true; + resourceList = newResourceList; // volatile write last — establishes happens-before for all above } /** @@ -121,7 +125,9 @@ public void ensureLoaded() { } catch (InterruptedException ex) { logger.error("Interrupted", ex); } - refresh(ApiCache.retrieveResponseSync(new QueryRef(QueryApiAccess.GET_MAINTAINED_RESOURCES), true)); + if (resourceList == null) { // double-check after potential wait + refresh(ApiCache.retrieveResponseSync(new QueryRef(QueryApiAccess.GET_MAINTAINED_RESOURCES), true)); + } } } diff --git a/src/main/java/com/knowledgepixels/nanodash/repository/SpaceRepository.java b/src/main/java/com/knowledgepixels/nanodash/repository/SpaceRepository.java index 0876876c..755e71d9 100644 --- a/src/main/java/com/knowledgepixels/nanodash/repository/SpaceRepository.java +++ b/src/main/java/com/knowledgepixels/nanodash/repository/SpaceRepository.java @@ -37,7 +37,7 @@ public static SpaceRepository get() { private Map> subspaceMap; private Map> superspaceMap; private boolean loaded = false; - private Long runRootUpdateAfter = null; + private volatile Long runRootUpdateAfter = null; private final Object loadLock = new Object(); @@ -51,27 +51,35 @@ private SpaceRepository() { */ public synchronized void refresh(ApiResponse resp) { logger.info("Refreshing spaces from API response with {} entries", resp.getData().size()); - spaceList = new ArrayList<>(); - spaceListByType = new HashMap<>(); - spacesById = new HashMap<>(); - subspaceMap = new HashMap<>(); - superspaceMap = new HashMap<>(); + List newSpaceList = new ArrayList<>(); + Map> newSpaceListByType = new HashMap<>(); + Map newSpacesById = new HashMap<>(); + Map> newSubspaceMap = new HashMap<>(); + Map> newSuperspaceMap = new HashMap<>(); for (ApiResponseEntry entry : resp.getData()) { Space space; space = SpaceFactory.getOrCreate(entry); - spaceList.add(space); - spaceListByType.computeIfAbsent(space.getType(), k -> new ArrayList<>()).add(space); - spacesById.put(space.getId(), space); + newSpaceList.add(space); + newSpaceListByType.computeIfAbsent(space.getType(), k -> new ArrayList<>()).add(space); + newSpacesById.put(space.getId(), space); } - SpaceFactory.removeStale(spacesById.keySet()); - for (Space space : spaceList) { - Space superSpace = this.getIdSuperspace(space); + SpaceFactory.removeStale(newSpacesById.keySet()); + for (Space space : newSpaceList) { + String id = space.getId(); + if (!id.matches("https?://[^/]+/.*/[^/]*/?")) continue; + String superId = id.replaceFirst("(https?://[^/]+/.*)/[^/]*/?", "$1"); + Space superSpace = newSpacesById.get(superId); if (superSpace == null) continue; - subspaceMap.computeIfAbsent(superSpace, k -> new HashSet<>()).add(space); - superspaceMap.computeIfAbsent(space, k -> new HashSet<>()).add(superSpace); + newSubspaceMap.computeIfAbsent(superSpace, k -> new HashSet<>()).add(space); + newSuperspaceMap.computeIfAbsent(space, k -> new HashSet<>()).add(superSpace); space.setDataNeedsUpdate(); } + spacesById = newSpacesById; + spaceListByType = newSpaceListByType; + subspaceMap = newSubspaceMap; + superspaceMap = newSuperspaceMap; loaded = true; + spaceList = newSpaceList; // volatile write last — establishes happens-before for all above } /** @@ -101,7 +109,9 @@ public void ensureLoaded() { } catch (InterruptedException ex) { logger.error("Interrupted", ex); } - refresh(ApiCache.retrieveResponseSync(new QueryRef(QueryApiAccess.GET_SPACES), true)); + if (spaceList == null) { // double-check after potential wait + refresh(ApiCache.retrieveResponseSync(new QueryRef(QueryApiAccess.GET_SPACES), true)); + } } }