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.impl.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.identifiers.InternalIdentifierConverter; 032import org.slf4j.Logger; 033 034import com.google.common.base.Function; 035 036/** 037 * Injects and extracts segments of hierarchy before the last segment of a 038 * multi-part identifier to ensure efficient performance of the JCR. 039 * 040 * @author ajs6f 041 * @since Mar 26, 2014 042 */ 043public class HierarchyConverter extends InternalIdentifierConverter { 044 045 public static final String DEFAULT_SEPARATOR = "/"; 046 047 private String separator = DEFAULT_SEPARATOR; 048 049 private String prefix = ""; 050 051 private final Function<String, String> addPrefix = new Function<String, String>() { 052 053 @Override 054 public String apply(final String input) { 055 return prefix + input; 056 } 057 }; 058 059 private static final int DEFAULT_LENGTH = 2; 060 061 private static final int DEFAULT_COUNT = 4; 062 063 private int length = DEFAULT_LENGTH; 064 065 private int levels = DEFAULT_COUNT; 066 067 private static final Logger log = getLogger(HierarchyConverter.class); 068 069 /* 070 * (non-Javadoc) 071 * @see com.google.common.base.Converter#doBackward(java.lang.Object) 072 */ 073 @Override 074 protected String doBackward(final String flat) { 075 log.debug("Converting incoming identifier: {}", flat); 076 final List<String> hierarchySegments = createHierarchySegments(); 077 final List<String> flatSegments = asList(flat.split(separator)); 078 List<String> firstSegments = emptyList(); 079 List<String> lastSegment = emptyList(); 080 if (flatSegments.size() == 0) { 081 // either empty identifier or separator identifier 082 return on(separator).join(hierarchySegments); 083 } 084 if (flatSegments.size() > 1) { 085 lastSegment = singletonList(getLast(flatSegments)); 086 firstSegments = flatSegments.subList(0, flatSegments.size() - 1); 087 } else { 088 // just one segment 089 lastSegment = singletonList(flatSegments.get(0)); 090 } 091 final Iterable<String> allSegments = concat(firstSegments, hierarchySegments, lastSegment); 092 093 return on(separator).join(allSegments); 094 } 095 096 /* 097 * (non-Javadoc) 098 * @see com.google.common.base.Converter#doForward(java.lang.Object) 099 */ 100 @Override 101 protected String doForward(final String hierarchical) { 102 log.debug("Converting outgoing identifier: {}", hierarchical); 103 final List<String> segments = asList(hierarchical.split(separator)); 104 if (segments.size() <= levels) { 105 // must be a root identifier 106 return ""; 107 } 108 List<String> firstSegments = emptyList(); 109 List<String> lastSegment = emptyList(); 110 if (segments.size() > levels + 1) { 111 // we subtract one for the final segment, then levels for the 112 // inserted hierarchy segments we want to remove 113 firstSegments = segments.subList(0, segments.size() - 1 - levels); 114 lastSegment = singletonList(getLast(segments)); 115 } else { 116 // just the trailing non-hierarchical segment 117 lastSegment = singletonList(getLast(segments)); 118 } 119 return on(separator).join(concat(firstSegments, lastSegment)); 120 } 121 122 private List<String> createHierarchySegments() { 123 // offers a list of segments sliced out of a UUID, each prefixed 124 if (levels == 0) { 125 return emptyList(); 126 } 127 return transform(fixedLength(length).splitToList(createHierarchyCharacterBlock()), addPrefix); 128 } 129 130 private CharSequence createHierarchyCharacterBlock() { 131 return randomUUID().toString().replace("-", "").substring(0, length * levels); 132 } 133 134 /** 135 * @param sep the separator to use 136 */ 137 public void setSeparator(final String sep) { 138 this.separator = sep; 139 } 140 141 /** 142 * @param l the length to set 143 */ 144 public void setLength(final int l) { 145 if (l < 1) { 146 throw new IllegalArgumentException("Segment length must be at least one!"); 147 } 148 this.length = l; 149 } 150 151 /** 152 * @param l the levels to set 153 */ 154 public void setLevels(final int l) { 155 this.levels = l; 156 } 157 158 /** 159 * @param p the prefix to set 160 */ 161 public void setPrefix(final String p) { 162 this.prefix = p; 163 } 164 165}