Skip to content

Commit

Permalink
Add generic support to the HierarchyConverter for
Browse files Browse the repository at this point in the history
  converting/reverting namespaces that occurs in the path like
  jcr:system, jcr:versionStorage, jcr:content etc.
  • Loading branch information
lsitu authored and Andrew Woods committed May 19, 2014
1 parent f3ba1eb commit e629670
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 29 deletions.
Expand Up @@ -17,13 +17,11 @@

import static com.google.common.base.Joiner.on;
import static com.google.common.base.Splitter.fixedLength;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Lists.transform;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.fcrepo.jcr.FedoraJcrTypes.FCR_CONTENT;
import static org.modeshape.jcr.api.JcrConstants.JCR_CONTENT;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.slf4j.LoggerFactory.getLogger;

import java.security.MessageDigest;
Expand Down Expand Up @@ -70,6 +68,9 @@ public String apply(final String input) {

private static final Logger log = getLogger(HierarchyConverter.class);

private static final String JCR_NAMESPACE_PREFIX = "jcr";
private static final String FCR_NAMESPACE_PREFIX = "fcr";

/*
* Convert an incoming path to auto-hierarchy path for JCR backend storage.
* For example: /parent/child => /xx/xx/parent/xx/xx/child
Expand All @@ -79,35 +80,41 @@ public String apply(final String input) {
protected String doBackward(final String flat) {
log.debug("Converting incoming identifier: {}", flat);
String nonContentSuffixed = flat;
final boolean isContentFile = flat.endsWith(FCR_CONTENT) || flat.endsWith(JCR_CONTENT);
if (nonContentSuffixed.startsWith(separator)) {
nonContentSuffixed = nonContentSuffixed.substring(1, nonContentSuffixed.length());
}
final List<String> flatSegments = asList(nonContentSuffixed.split(separator));
List<String> hierarchySegments = null;
final List<String> jcrPathSegments = new ArrayList<>();
int pathSize = flatSegments.size();
if (isContentFile) {
jcrPathSegments.add(JCR_CONTENT);
// Subtract the fcr:content namespace element for auto-hierarchy
pathSize -= 1;
}
if (pathSize == 0) {
// either empty identifier or separator identifier
return nonContentSuffixed;
}
for (int i = pathSize - 1; i >= 0; i--) {
final String seg = flatSegments.get(i);
if (seg == null || seg.length() == 0) {
boolean isVersionRelate = false;
for (String seg : flatSegments) {
if (isBlank(seg)) {
if (pathSize == 1) {
return flat;
}
} else if (isVersionRelate) {
// Skip translation after version syntax detected for now
jcrPathSegments.add(seg);
} else if (isFCRNamespace(seg)) {
// FCR related namspaces
jcrPathSegments.add(convertNamespace(seg));
if (seg.indexOf(":versionStorage") >= 0 || seg.indexOf(":versions") > 0) {
isVersionRelate = true;
}
} else if (seg.startsWith("[") && seg.endsWith("]")) {
// System related syntax?
jcrPathSegments.add(seg);
} else {
// Create the auto-hierarchy path segments, add them to
// the list in front of the transparent path segment
hierarchySegments = createHierarchySegments(hashKey(singletonList(seg)));
jcrPathSegments.add(0, flatSegments.get(i));
jcrPathSegments.addAll(0, hierarchySegments);
jcrPathSegments.addAll(hierarchySegments);
jcrPathSegments.add(seg);
}
}
final String pathConverted = on(separator).join(jcrPathSegments);
Expand All @@ -129,39 +136,55 @@ protected String doForward(final String hierarchical) {
}
final List<String> jcrPathSegments = asList(nonContentSuffixed.split(separator));
int jcrPathSize = jcrPathSegments.size();
final boolean isContentFile = hierarchical.endsWith(JCR_CONTENT);
if (isContentFile) {
jcrPathSize -= 1;
}
if (jcrPathSize <= levels) {
// must be a root identifier
return hierarchical;
}
boolean isVersionRelate = false;
// Convert the auto-hierarchy path to transparent path
final List<String> pathSegments = new ArrayList<>();
List<String> hierarchySegments = null;
for (int i = levels ; i < jcrPathSize; i += levels + 1) {
final List<String> hierarchySegments = new ArrayList<>();
for (int i = 0 ; i < jcrPathSize; i++) {
final String seg = jcrPathSegments.get(i);
if (seg == null || seg.length() == 0) {
final int hierarchySize = hierarchySegments.size();
if (isBlank(seg)) {
// If the segment null or empty, must be double slash or something wrong.
// Add it as empty that will produce an slash for now?
pathSegments.add("");
} else {
hierarchySegments.clear();
} else if (isVersionRelate) {
// Skip translation after version syntax detected for now
pathSegments.add(seg);
} else if (isJCRNamespace(seg)) {
// Convert namespace
pathSegments.add(convertNamespace(seg));
if (seg.indexOf(":versionStorage") >= 0 || seg.indexOf(":versions") > 0) {
isVersionRelate = true;
}
if (hierarchySize > 0) {
// Something wrong
log.error("Outgoing hierarchy {} in path {} has no match.",
on(separator).join(hierarchySegments), jcrPathSegments.subList(0, i));
hierarchySegments.clear();
}
} else if (hierarchySize == levels) {
// Completed a hierarchy sub-part conversion
pathSegments.add(seg);
hierarchySegments = jcrPathSegments.subList(i - levels, i);
final String hierarchyPath = on(separator).join(hierarchySegments);
final String hashPath = on(separator).join(createHierarchySegments(seg));
if (!hierarchyPath.equals(hashPath)) {
log.error("Outgoing sub path {} hierarchy {} (got {}) doesn't match the path segament {}.",
log.error("Outgoing sub path {} hierarchy {} (got {}) doesn't match the path segment {}.",
on(separator).join(jcrPathSegments.subList(0, i + 1)), hierarchyPath, hashPath, seg);
}
// Clear the hierarchy segments
hierarchySegments.clear();
} else {
// A hierarchy segment
hierarchySegments.add(seg);
}
}

if (isContentFile) {
//Don't forget the fcr:content segment for content files
pathSegments.add(FCR_CONTENT);
}

final String pathConverted = on(separator).join(pathSegments);
log.trace("Converted outgoing identifier \"{}\" to \"{}\".", hierarchical, pathConverted);
return "/" + pathConverted;
Expand All @@ -186,7 +209,7 @@ private CharSequence createHierarchyCharacterBlock(final String path) {
md = MessageDigest.getInstance("SHA-1");
md.reset();
md.update(path.getBytes("utf8"));
hierarchyPath = byteToHex(md.digest()).replace("-", "").substring(0, length * levels);
hierarchyPath = byteToHex(md.digest()).substring(0, length * levels);
} catch (final Exception e) {
log.error("Hierarchy conversion auto-hierarchy", e);
}
Expand Down Expand Up @@ -234,4 +257,36 @@ public void setPrefix(final String p) {
this.prefix = p;
}

/**
* @param value
* @return
*/
public boolean isJCRNamespace(final String value) {
return value.split(":")[0].equals(JCR_NAMESPACE_PREFIX);
}

/**
* @param value
* @return
*/
public boolean isFCRNamespace(final String value) {
return value.split(":")[0].equals(FCR_NAMESPACE_PREFIX);
}

/**
* Convert namespaces
* @param namespace
* @return
*/
public String convertNamespace(String namespace) {
final String[] tokens = namespace.split(":");
final int length = tokens.length;
if (tokens[0].equals(JCR_NAMESPACE_PREFIX) && length == 2 ) {
return FCR_NAMESPACE_PREFIX + ":" + tokens[1];
} else if (tokens[0].equals(FCR_NAMESPACE_PREFIX) && length == 2 ) {
return JCR_NAMESPACE_PREFIX + ":" + tokens[1];
} else {
return namespace;
}
}
}
Expand Up @@ -22,6 +22,7 @@
import static java.util.regex.Pattern.compile;
import static org.fcrepo.jcr.FedoraJcrTypes.FCR_CONTENT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.modeshape.jcr.api.JcrConstants.JCR_CONTENT;
Expand Down Expand Up @@ -104,6 +105,51 @@ public void testRoundTrip(final byte levels, final byte length) {
assertEquals("Didn't get original back!", testId, shouldBeOriginal);
}

@Test
public void testIncomingSlashes() {
testTranslator.setLevels(DEFAULT_LEVEL);
testTranslator.setLength(DEFAULT_LENGTH);
final String incomingPath = "///" + testId;
final String expectedPath = testTranslator.reverse().convert(testId);
final String result = testTranslator.reverse().convert(incomingPath);
log.debug("Converted incoming path {} to {} expected {}!", incomingPath, result, expectedPath);
assertEquals("Didn't get the expected result for path with slashes "
+ incomingPath + "!", expectedPath, result);
}

@Test
public void testOutgoingNamespaceConvert() {
testTranslator.setLevels(DEFAULT_LEVEL);
testTranslator.setLength(DEFAULT_LENGTH);
final String outgoingPath = "/jcr:system/jcr:versionStorage" + testId;
final String expectedPath = "/fcr:system/fcr:versionStorage" + testId;
final String result = testTranslator.convert(outgoingPath);
log.debug("Converted out going path {} to {} expected {}!", outgoingPath, result, expectedPath);
assertEquals("Shouldn't change the version related path for /jcr:system/jcr:versionStorage "
+ outgoingPath + "!", expectedPath, result);
}

@Test
public void testIncomingNamespaceReverse() {
testTranslator.setLevels(DEFAULT_LEVEL);
testTranslator.setLength(DEFAULT_LENGTH);
final String incomingPath = testId + "/" + "fcr:versions";
final String expectedPath = testTranslator.reverse().convert(testId) + "/" + "jcr:versions";
final String result = testTranslator.reverse().convert(incomingPath);
log.debug("Converted incoming path {} to {} expected {}!", incomingPath, expectedPath, result);
assertEquals("Didn't get the expected result for path with namespace " + incomingPath + "!", expectedPath, result);
}

@Test
public void testNamespaceRoundTrip() {
testTranslator.setLevels(DEFAULT_LEVEL);
testTranslator.setLength(DEFAULT_LENGTH);
final String incomingPath = testId + "/" + "fcr:versions" + testId;
final String result = testTranslator.convert(testTranslator.reverse().convert(incomingPath));
log.debug("Converted incoming path {} round trip {}!", incomingPath, result);
assertEquals("Didn't get the same result for round trip convert with namespace " + incomingPath + "!", result, incomingPath);
}

@Test
public void sameIncomingPathOutput() {
testTranslator.setLevels(DEFAULT_LEVEL);
Expand Down Expand Up @@ -188,4 +234,33 @@ public void testContentPaths() {
result = testTranslator.convert(internalTestId);
assertEquals("Should have swapped content suffix to FCR!", externalTestId, result);
}

@Test
public void testIsJcrNamespace() {
boolean result = testTranslator.isJCRNamespace("jcr:namespace");
assertTrue("Should be JCR namespace", result);
result = testTranslator.isJCRNamespace("fcr:namespace");
assertFalse("Should not be JCR namespace", result);
}

@Test
public void testIsFcrNamespace() {
boolean result = testTranslator.isFCRNamespace("fcr:namespace");
assertTrue("Should be FCR namespace", result);
result = testTranslator.isFCRNamespace("jcr:namespace");
assertFalse("Should not be FCR namespace", result);
}

@Test
public void testConvertNamespace() {
String testNamespace = "jcr:namespace";
String result = testTranslator.convertNamespace(testNamespace);
assertEquals("Should be converted to FCR namespace!", "fcr:namespace", result);
testNamespace = "fcr:namespace";
result = testTranslator.convertNamespace(testNamespace);
assertEquals("Should be converted to JCR namespace!", "jcr:namespace", result);
testNamespace = "a:namespace";
result = testTranslator.convertNamespace(testNamespace);
assertEquals("Should not change this namespace!", testNamespace, result);
}
}

0 comments on commit e629670

Please sign in to comment.