001/** 002 * Copyright 2015 DuraSpace, Inc. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.fcrepo.kernel.modeshape.identifiers; 017 018import static com.google.common.base.Joiner.on; 019import static com.google.common.base.Splitter.fixedLength; 020import static com.google.common.collect.Iterables.concat; 021import static com.google.common.collect.Iterables.getLast; 022import static com.google.common.collect.Lists.transform; 023import static java.util.Arrays.asList; 024import static java.util.Collections.emptyList; 025import static java.util.Collections.singletonList; 026import static java.util.UUID.randomUUID; 027import static org.slf4j.LoggerFactory.getLogger; 028 029import java.util.List; 030 031import org.fcrepo.kernel.api.identifiers.InternalIdentifierConverter; 032import org.slf4j.Logger; 033 034/** 035 * Injects and extracts segments of hierarchy before the last segment of a 036 * multi-part identifier to ensure efficient performance of the JCR. 037 * 038 * @author ajs6f 039 * @since Mar 26, 2014 040 */ 041public class HierarchyConverter extends InternalIdentifierConverter { 042 043 public static final String DEFAULT_SEPARATOR = "/"; 044 045 private String separator = DEFAULT_SEPARATOR; 046 047 private String prefix = ""; 048 049 private static final int DEFAULT_LENGTH = 2; 050 051 private static final int DEFAULT_COUNT = 4; 052 053 private int length = DEFAULT_LENGTH; 054 055 private int levels = DEFAULT_COUNT; 056 057 private static final Logger log = getLogger(HierarchyConverter.class); 058 059 /* 060 * (non-Javadoc) 061 * @see com.google.common.base.Converter#doBackward(java.lang.Object) 062 */ 063 @Override 064 protected String doBackward(final String flat) { 065 log.debug("Converting incoming identifier: {}", flat); 066 final List<String> hierarchySegments = createHierarchySegments(); 067 final List<String> flatSegments = asList(flat.split(separator)); 068 List<String> firstSegments = emptyList(); 069 List<String> lastSegment = emptyList(); 070 if (flatSegments.size() == 0) { 071 // either empty identifier or separator identifier 072 return on(separator).join(hierarchySegments); 073 } 074 if (flatSegments.size() > 1) { 075 lastSegment = singletonList(getLast(flatSegments)); 076 firstSegments = flatSegments.subList(0, flatSegments.size() - 1); 077 } else { 078 // just one segment 079 lastSegment = singletonList(flatSegments.get(0)); 080 } 081 final Iterable<String> allSegments = concat(firstSegments, hierarchySegments, lastSegment); 082 083 return on(separator).join(allSegments); 084 } 085 086 /* 087 * (non-Javadoc) 088 * @see com.google.common.base.Converter#doForward(java.lang.Object) 089 */ 090 @Override 091 protected String doForward(final String hierarchical) { 092 log.debug("Converting outgoing identifier: {}", hierarchical); 093 final List<String> segments = asList(hierarchical.split(separator)); 094 if (segments.size() <= levels) { 095 // must be a root identifier 096 return ""; 097 } 098 List<String> firstSegments = emptyList(); 099 List<String> lastSegment = emptyList(); 100 if (segments.size() > levels + 1) { 101 // we subtract one for the final segment, then levels for the 102 // inserted hierarchy segments we want to remove 103 firstSegments = segments.subList(0, segments.size() - 1 - levels); 104 lastSegment = singletonList(getLast(segments)); 105 } else { 106 // just the trailing non-hierarchical segment 107 lastSegment = singletonList(getLast(segments)); 108 } 109 return on(separator).join(concat(firstSegments, lastSegment)); 110 } 111 112 private List<String> createHierarchySegments() { 113 // offers a list of segments sliced out of a UUID, each prefixed 114 if (levels == 0) { 115 return emptyList(); 116 } 117 return transform(fixedLength(length).splitToList(createHierarchyCharacterBlock()), x -> prefix + x); 118 } 119 120 private CharSequence createHierarchyCharacterBlock() { 121 return randomUUID().toString().replace("-", "").substring(0, length * levels); 122 } 123 124 /** 125 * @param sep the separator to use 126 */ 127 public void setSeparator(final String sep) { 128 this.separator = sep; 129 } 130 131 /** 132 * @param l the length to set 133 */ 134 public void setLength(final int l) { 135 if (l < 1) { 136 throw new IllegalArgumentException("Segment length must be at least one!"); 137 } 138 this.length = l; 139 } 140 141 /** 142 * @param l the levels to set 143 */ 144 public void setLevels(final int l) { 145 this.levels = l; 146 } 147 148 /** 149 * @param p the prefix to set 150 */ 151 public void setPrefix(final String p) { 152 this.prefix = p; 153 } 154 155}