ChecklistExporter { public void updateChecklist(final WriterOpener writerOpener, final Session session, final Checklist checklist, final ForcedRootUpdatePolicy updatePolicy) throws IOException, RepositoryException { final List<JcrNs> chkNs = new ArrayList<>(); if (checklist != null && checklist.getJcrNamespaces() != null) { chkNs.addAll(checklist.getJcrNamespaces()); } final NamespaceMapping origMapping = JsonCnd.toNamespaceMapping(chkNs); final NamespaceMapping remapping = JsonCnd.toNamespaceMapping(jcrNamespaces); ensureNamespaces(session, origMapping); ensureNamespaces(session, remapping); final List<ForcedRoot> newRoots = findRoots(session); Predicate<ForcedRoot> retainFilter = getRetainFilter(updatePolicy); final JsonObjectBuilder builder = Json.createObjectBuilder(); final Map<String, ForcedRoot> existing = new LinkedHashMap<>(); final List<PrivilegeDefinition> privileges = new ArrayList<>(); if (checklist != null) { checklist.toJson().forEach(builder::add); privileges.addAll(checklist.getJcrPrivileges()); checklist.getForcedRoots().stream() .map(nsRemapForcedRoot(origMapping, remapping)) .filter(retainFilter) .forEachOrdered(root -> existing.put(root.getPath(), root)); } final NamespaceMappingRequest.Builder request = new NamespaceMappingRequest.Builder(); if (!privileges.isEmpty()) { builder.add(Checklist.keys().jcrPrivileges(), JsonCnd.privilegesToJson(privileges, origMapping)); privileges.stream().flatMap(JsonCnd::namedBy).forEach(request::withQName); } newRoots.forEach(root -> existing.put(root.getPath(), root)); final List<ForcedRoot> forcedRoots = new ArrayList<>(existing.values()); Collections.sort(forcedRoots); final JsonArray forcedRootsJson = forcedRoots.stream() .map(ForcedRoot::toJson) .collect(JsonCollectors.toJsonArray()); builder.add(Checklist.keys().forcedRoots(), forcedRootsJson); final NamePathResolver resolver = new DefaultNamePathResolver(session); final Set<Name> builtinNodetypes = CndExporter.BUILTIN_NODETYPES.stream() .map(uncheck1(resolver::getQName)) .filter(Objects::nonNull) .collect(Collectors.toSet()); final List<Name> foundNodeTypes = forcedRoots.stream() .reduce(new HashSet<>(), ChecklistExporter::findNodeTypesInForcedRoot, addToLeft()).stream() .map(uncheck1(resolver::getQName)) .collect(Collectors.toList()); final Map<Name, NodeTypeDefinition> exportedNodeTypes = CndExporter.retrieveNodeTypes(session, foundNodeTypes, exportTypeDefSelector()); final List<QNodeTypeDefinition> qNodeTypes = exportedNodeTypes.entrySet().stream() .map(mapValue(JsonCnd.adaptToQ(session))) .filter(testKey(((Predicate<Name>) builtinNodetypes::contains).negate())) .map(Map.Entry::getValue) .collect(Collectors.toList()); final NamespaceMapping spm = new NamespaceMapping(new SessionNamespaceResolver(session)); if (!qNodeTypes.isEmpty()) { final Set<String> nsUris = qNodeTypes.stream() .flatMap(JsonCnd::namedBy) .map(Name::getNamespaceURI) .collect(Collectors.toSet()); final Map<String, String> uriMapping = remapping.getURIToPrefixMapping(); nsUris.forEach(Fun.uncheckVoid1(uri -> { final String prefix = uriMapping.containsKey(uri) ? uriMapping.get(uri) : spm.getPrefix(uri); request.withRetainPrefix(prefix); spm.setMapping(prefix, uri); })); final JsonObject jcrNodetypes = JsonCnd.toJson(qNodeTypes, new NamespaceMapping(new SessionNamespaceResolver(session))); builder.add(Checklist.keys().jcrNodetypes(), jcrNodetypes); } final Set<String> forcedRootPrefixes = forcedRoots.stream() .reduce(new HashSet<>(), ChecklistExporter::findJcrPrefixesInForcedRoot, addToLeft()); forcedRootPrefixes.forEach(request::withRetainPrefix); final List<JcrNs> exportNamespaces = request.build() .resolveToJcrNs(spm).stream() .flatMap(Result::stream) .collect(Collectors.toList()); if (!exportNamespaces.isEmpty()) { builder.add(Checklist.keys().jcrNamespaces(), JavaxJson.wrap(exportNamespaces)); } final JsonObject sorted = builder.build().entrySet().stream() .sorted(Checklist.comparingJsonKeys(Map.Entry::getKey)) .collect(JsonCollectors.toJsonObject()); try (Writer writer = writerOpener.open(); JsonWriter jsonWriter = Json .createWriterFactory(Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true)) .createWriter(writer)) { jsonWriter.writeObject(sorted); } } private  ChecklistExporter(final List<Op> operations,
                              final List<String> exportTypeDefs,
                              final List<Rule> pathScopes,
                              final List<Rule> nodeTypeFilters,
                              final List<JcrNs> jcrNamespaces); void updateChecklist(final WriterOpener writerOpener,
                                final Session session,
                                final Checklist checklist,
                                final ForcedRootUpdatePolicy updatePolicy); List<ForcedRoot> findRoots(final Session session); static final ForcedRootUpdatePolicy DEFAULT_UPDATE_POLICY; static final String COVARIANT_PREFIX; }
@Test public void testUpdateChecklist() throws Exception { final String pathPrefix = "/test"; final File tempDir = new File(testBaseDir, "testUpdateChecklist"); final File pass1Dir = new File(tempDir, "pass1/segmentstore"); final File pass1Checklist = new File(tempDir, "pass1.chk.json"); final File fullRepoDir = new File(tempDir, "fullRepo/segmentstore"); final File diffRepoDir = new File(tempDir, "diffRepo/segmentstore"); final File fullPassChecklist = new File(tempDir, "fullPass.chk.json"); final File mergePassChecklist = new File(tempDir, "mergePass.chk.json"); final File replacePassChecklist = new File(tempDir, "replacePass.chk.json"); final File truncatePassChecklist = new File(tempDir, "truncatePass.chk.json"); final int sample = 5; List<String> orderedPaths = IntStream.range(0, sample).mapToObj(val -> pathPrefix + "/ordered" + val).collect(Collectors.toList()); List<String> unorderedPaths = IntStream.range(0, sample).mapToObj(val -> pathPrefix + "/unordered" + val).collect(Collectors.toList()); assertEquals("should generate n paths: ", sample, orderedPaths.size()); final List<String> allPaths = new ArrayList<>(orderedPaths); allPaths.addAll(unorderedPaths); allPaths.add(pathPrefix); TestUtil.prepareRepo(pass1Dir, session -> { TestUtil.installCndFromURL(session, getClass().getResource("/sling_nodetypes.cnd")); orderedPaths.forEach(Fun.tryOrVoid1(path -> JcrUtils.getOrCreateByPath(path, "nt:folder", "sling:OrderedFolder", session, true))); unorderedPaths.forEach(Fun.tryOrVoid1(path -> JcrUtils.getOrCreateByPath(path, "nt:folder", "sling:Folder", session, true))); }); TestUtil.prepareRepo(fullRepoDir, session -> { TestUtil.installCndFromURL(session, getClass().getResource("/sling_nodetypes.cnd")); allPaths.forEach(Fun.uncheckVoid1(path -> { Node node = JcrUtils.getOrCreateByPath(path, "sling:Folder", session); node.addMixin("mix:title"); node.addMixin("sling:Resource"); })); session.save(); }); TestUtil.prepareRepo(diffRepoDir, session -> { TestUtil.installCndFromURL(session, getClass().getResource("/sling_nodetypes.cnd")); allPaths.stream().filter(path -> path.matches(".*[02468]$")) .forEachOrdered(Fun.uncheckVoid1(path -> { Node node = JcrUtils.getOrCreateByPath(path, "sling:Folder", session); node.addMixin("mix:title"); node.addMixin("sling:Resource"); })); session.save(); }); TestUtil.withReadOnlyFixture(pass1Dir, session -> { ChecklistExporter pathExporter = new ChecklistExporter.Builder().byPath(allPaths.toArray(new String[0])).build(); pathExporter.updateChecklist(() -> new OutputStreamWriter( new FileOutputStream(pass1Checklist), StandardCharsets.UTF_8), session, Checklist.fromJson("", null, obj() .key(Checklist.keys().jcrNamespaces(), Collections .singletonList(JcrNs.create("sling", "http: .key(Checklist.keys().jcrPrivileges(), obj() .key("sling:doesAll", obj() .key("contains", arr("sling:doOne", "sling:doTwo"))) .get()) .get()), null); try (JsonReader reader = Json.createReader(new FileInputStream(pass1Checklist))) { JsonObject checklist = reader.readObject(); assertTrue("checklist object should contain the forcedRoots key", checklist.containsKey(Checklist.keys().forcedRoots())); assertTrue("checklist object should contain the privileges key", checklist.containsKey(Checklist.keys().jcrPrivileges())); JsonArray forcedRoots = checklist.getJsonArray(Checklist.keys().forcedRoots()); assertEquals("forcedRoots should be array with expected number of elements", allPaths.size(), forcedRoots.size()); List<ForcedRoot> readRoots = JavaxJson.mapArrayOfObjects(forcedRoots, ForcedRoot::fromJson); for (String path : unorderedPaths) { ForcedRoot root0 = readRoots.stream().filter(root -> path.equals(root.getPath())).findFirst().orElse(null); assertNotNull(String.format("[pass1] root for path %s should not be null", path), root0); assertEquals(String.format("[pass1] root primaryType for path %s should be sling:Folder", path), "sling:Folder", root0.getPrimaryType()); } for (String path : orderedPaths) { ForcedRoot root0 = readRoots.stream().filter(root -> path.equals(root.getPath())).findFirst().orElse(null); assertNotNull(String.format("[pass1] root for path %s should not be null", path), root0); assertEquals(String.format("[pass1] root primaryType for path %s should be sling:OrderedFolder", path), "sling:OrderedFolder", root0.getPrimaryType()); } } }); TestUtil.withReadOnlyFixture(fullRepoDir, session -> { ChecklistExporter pathExporter = new ChecklistExporter.Builder().byPath(allPaths.toArray(new String[0])).build(); try (JsonReader reader = Json.createReader(new FileInputStream(pass1Checklist))) { pathExporter.updateChecklist(() -> new OutputStreamWriter( new FileOutputStream(fullPassChecklist), StandardCharsets.UTF_8), session, Checklist.fromJson("", null, reader.readObject()), null); } try (JsonReader reader = Json.createReader(new FileInputStream(fullPassChecklist))) { JsonObject checklist = reader.readObject(); assertTrue("checklist object should contain the forcedRoots key", checklist.containsKey(Checklist.keys().forcedRoots())); assertTrue("checklist object should contain the privileges key", checklist.containsKey(Checklist.keys().jcrPrivileges())); JsonArray forcedRoots = checklist.getJsonArray(Checklist.keys().forcedRoots()); assertEquals("forcedRoots should be array with expected number of elements", allPaths.size(), forcedRoots.size()); List<ForcedRoot> readRoots = JavaxJson.mapArrayOfObjects(forcedRoots, ForcedRoot::fromJson); for (String path : allPaths) { ForcedRoot root0 = readRoots.stream().filter(root -> path.equals(root.getPath())).findFirst().orElse(null); assertNotNull(String.format("[pass2] root for path %s should not be null", path), root0); assertEquals(String.format("[pass2] root primaryType for path %s should be sling:Folder", path), "sling:Folder", root0.getPrimaryType()); assertTrue(String.format("[pass2] root mixinTypes for path %s should contain mix:title", path), root0.getMixinTypes().contains("mix:title")); } } }); TestUtil.withReadOnlyFixture(diffRepoDir, session -> { ChecklistExporter diffExporter = new ChecklistExporter.Builder() .byNodeType("sling:Folder") .withScopePaths(Rules.fromJsonArray(arr(key("type", "include").key("pattern", pathPrefix + "/ordered.*")).get())) .withNodeTypeFilters(Rules.fromJsonArray(arr(key("type", "exclude").key("pattern", "sling:.*")).get())) .build(); try (JsonReader reader = Json.createReader(new FileInputStream(fullPassChecklist))) { diffExporter.updateChecklist(() -> new OutputStreamWriter( new FileOutputStream(mergePassChecklist), StandardCharsets.UTF_8), session, Checklist.fromJson("", null, reader.readObject()), ChecklistExporter.ForcedRootUpdatePolicy.MERGE); } try (JsonReader reader = Json.createReader(new FileInputStream(fullPassChecklist))) { diffExporter.updateChecklist(() -> new OutputStreamWriter( new FileOutputStream(replacePassChecklist), StandardCharsets.UTF_8), session, Checklist.fromJson("", null, reader.readObject()), ChecklistExporter.ForcedRootUpdatePolicy.REPLACE); } try (JsonReader reader = Json.createReader(new FileInputStream(fullPassChecklist))) { diffExporter.updateChecklist(() -> new OutputStreamWriter( new FileOutputStream(truncatePassChecklist), StandardCharsets.UTF_8), session, Checklist.fromJson("", null, reader.readObject()), ChecklistExporter.ForcedRootUpdatePolicy.TRUNCATE); } try (JsonReader reader = Json.createReader(new FileInputStream(mergePassChecklist))) { JsonObject checklist = reader.readObject(); assertTrue("checklist object should contain the forcedRoots key", checklist.containsKey(Checklist.keys().forcedRoots())); JsonArray forcedRoots = checklist.getJsonArray(Checklist.keys().forcedRoots()); assertEquals("[mergePass] forcedRoots should be array with expected number of elements", allPaths.size(), forcedRoots.size()); List<ForcedRoot> readRoots = JavaxJson.mapArrayOfObjects(forcedRoots, ForcedRoot::fromJson); for (String path : allPaths) { ForcedRoot root0 = readRoots.stream().filter(root -> path.equals(root.getPath())).findFirst().orElse(null); if (path.matches(".*/ordered[02468]$")) { assertNotNull(String.format("[mergePass] root for path %s should not be null", path), root0); assertNull(String.format("[mergePass] root primaryType for path %s should be sling:Folder", path), root0.getPrimaryType()); assertTrue(String.format("[mergePass] root mixinTypes for path %s should contain mix:title", path), root0.getMixinTypes().contains("mix:title")); assertFalse(String.format("[mergePass] root mixinTypes for path %s should NOT contain sling:Resource", path), root0.getMixinTypes().contains("sling:Resource")); } else { assertNotNull(String.format("[mergePass] root for path %s should not be null", path), root0); assertEquals(String.format("[mergePass] root primaryType for path %s should be sling:Folder", path), "sling:Folder", root0.getPrimaryType()); assertTrue(String.format("[mergePass] root mixinTypes for path %s should contain mix:title", path), root0.getMixinTypes().contains("mix:title")); assertTrue(String.format("[mergePass] root mixinTypes for path %s should contain sling:Resource", path), root0.getMixinTypes().contains("sling:Resource")); } } } try (JsonReader reader = Json.createReader(new FileInputStream(replacePassChecklist))) { JsonObject checklist = reader.readObject(); assertTrue("checklist object should contain the forcedRoots key", checklist.containsKey(Checklist.keys().forcedRoots())); JsonArray forcedRoots = checklist.getJsonArray(Checklist.keys().forcedRoots()); assertEquals("[replacePass] forcedRoots should be array with expected number of elements", 1 + unorderedPaths.size() + orderedPaths.stream().filter(path -> path.matches(".*[02468]$")).count(), forcedRoots.size()); List<ForcedRoot> readRoots = JavaxJson.mapArrayOfObjects(forcedRoots, ForcedRoot::fromJson); for (String path : allPaths) { ForcedRoot root0 = readRoots.stream().filter(root -> path.equals(root.getPath())).findFirst().orElse(null); if (path.matches(".*/ordered[02468]$")) { assertNotNull(String.format("[replacePass] root for path %s should not be null", path), root0); assertNull(String.format("[replacePass] root primaryType for path %s should be sling:Folder", path), root0.getPrimaryType()); assertTrue(String.format("[replacePass] root mixinTypes for path %s should contain mix:title", path), root0.getMixinTypes().contains("mix:title")); assertFalse(String.format("[replacePass] root mixinTypes for path %s should NOT contain sling:Resource", path), root0.getMixinTypes().contains("sling:Resource")); } else if (path.equals(pathPrefix) || path.matches(".*/unordered.*")) { assertNotNull(String.format("[replacePass] root for path %s should not be null", path), root0); assertEquals(String.format("[replacePass] root primaryType for path %s should be sling:Folder", path), "sling:Folder", root0.getPrimaryType()); assertTrue(String.format("[replacePass] root mixinTypes for path %s should contain mix:title", path), root0.getMixinTypes().contains("mix:title")); assertTrue(String.format("[replacePass] root mixinTypes for path %s should contain sling:Resource", path), root0.getMixinTypes().contains("sling:Resource")); } else { assertNull(String.format("[replacePass] root for path %s should be null", path), root0); } } } try (JsonReader reader = Json.createReader(new FileInputStream(truncatePassChecklist))) { JsonObject checklist = reader.readObject(); assertTrue("checklist object should contain the forcedRoots key", checklist.containsKey(Checklist.keys().forcedRoots())); JsonArray forcedRoots = checklist.getJsonArray(Checklist.keys().forcedRoots()); assertEquals("[truncatePass] forcedRoots should be array with expected number of elements", orderedPaths.stream().filter(path -> path.matches(".*[02468]$")).count(), forcedRoots.size()); List<ForcedRoot> readRoots = JavaxJson.mapArrayOfObjects(forcedRoots, ForcedRoot::fromJson); for (String path : allPaths) { ForcedRoot root0 = readRoots.stream().filter(root -> path.equals(root.getPath())).findFirst().orElse(null); if (path.matches(".*/ordered[02468]$")) { assertNotNull(String.format("[truncatePass] root for path %s should not be null", path), root0); assertNull(String.format("[truncatePass] root primaryType for path %s should be sling:Folder", path), root0.getPrimaryType()); assertTrue(String.format("[truncatePass] root mixinTypes for path %s should contain mix:title", path), root0.getMixinTypes().contains("mix:title")); assertFalse(String.format("[truncatePass] root mixinTypes for path %s should NOT contain sling:Resource", path), root0.getMixinTypes().contains("sling:Resource")); } else { assertNull(String.format("[truncatePass] root for path %s should not be null", path), root0); } } } }); }