001package org.eclipse.aether.internal.impl.synccontext.named; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.util.Collection; 023import java.util.stream.Collectors; 024 025import org.eclipse.aether.RepositorySystemSession; 026import org.eclipse.aether.artifact.Artifact; 027import org.eclipse.aether.metadata.Metadata; 028import org.eclipse.aether.util.ConfigUtils; 029import org.eclipse.aether.util.StringDigestUtil; 030 031import static java.util.Objects.requireNonNull; 032 033/** 034 * Wrapping {@link NameMapper}, that wraps another {@link NameMapper} and hashes resulting strings. It makes use of 035 * fact that (proper) Hash will create unique fixed length string for each different input string (so injection still 036 * stands). This mapper produces file system friendly names. Supports different "depths" (0-4 inclusive) where the 037 * name will contain 0 to 4 level deep directories. 038 * <p> 039 * This mapper is usable in any scenario, but intent was to produce more "compact" name mapper for file locking. 040 * 041 * @since 1.9.0 042 */ 043public class HashingNameMapper implements NameMapper 044{ 045 private static final String CONFIG_PROP_DEPTH = "aether.syncContext.named.hashing.depth"; 046 047 private final NameMapper delegate; 048 049 public HashingNameMapper( final NameMapper delegate ) 050 { 051 this.delegate = requireNonNull( delegate ); 052 } 053 054 @Override 055 public boolean isFileSystemFriendly() 056 { 057 return true; // hashes delegated strings, so whatever it wrapped, it does not come through 058 } 059 060 @Override 061 public Collection<String> nameLocks( RepositorySystemSession session, 062 Collection<? extends Artifact> artifacts, 063 Collection<? extends Metadata> metadatas ) 064 { 065 final int depth = ConfigUtils.getInteger( session, 2, CONFIG_PROP_DEPTH ); 066 if ( depth < 0 || depth > 4 ) 067 { 068 throw new IllegalArgumentException( "allowed depth value is between 0 and 4 (inclusive)" ); 069 } 070 return delegate.nameLocks( session, artifacts, metadatas ).stream() 071 .map( n -> hashName( n, depth ) ) 072 .collect( Collectors.toList() ); 073 } 074 075 private String hashName( final String name, final int depth ) 076 { 077 String hashedName = StringDigestUtil.sha1( name ); 078 if ( depth == 0 ) 079 { 080 return hashedName; 081 } 082 StringBuilder prefix = new StringBuilder( "" ); 083 int i = 0; 084 while ( i < hashedName.length() && i / 2 < depth ) 085 { 086 prefix.append( hashedName, i, i + 2 ).append( "/" ); 087 i += 2; 088 } 089 return prefix.append( hashedName ).toString(); 090 } 091}