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