View Javadoc
1   package org.eclipse.aether.internal.impl.synccontext.named;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.Collection;
23  import java.util.stream.Collectors;
24  
25  import org.eclipse.aether.RepositorySystemSession;
26  import org.eclipse.aether.artifact.Artifact;
27  import org.eclipse.aether.metadata.Metadata;
28  import org.eclipse.aether.util.ConfigUtils;
29  import org.eclipse.aether.util.StringDigestUtil;
30  
31  import static java.util.Objects.requireNonNull;
32  
33  /**
34   * Wrapping {@link NameMapper}, that wraps another {@link NameMapper} and hashes resulting strings. It makes use of
35   * fact that (proper) Hash will create unique fixed length string for each different input string (so injection still
36   * stands). This mapper produces file system friendly names. Supports different "depths" (0-4 inclusive) where the
37   * name will contain 0 to 4 level deep directories.
38   * <p>
39   * This mapper is usable in any scenario, but intent was to produce more "compact" name mapper for file locking.
40   *
41   * @since 1.9.0
42   */
43  public class HashingNameMapper implements NameMapper
44  {
45      private static final String CONFIG_PROP_DEPTH = "aether.syncContext.named.hashing.depth";
46  
47      private final NameMapper delegate;
48  
49      public HashingNameMapper( final NameMapper delegate )
50      {
51          this.delegate = requireNonNull( delegate );
52      }
53  
54      @Override
55      public boolean isFileSystemFriendly()
56      {
57          return true; // hashes delegated strings, so whatever it wrapped, it does not come through
58      }
59  
60      @Override
61      public Collection<String> nameLocks( RepositorySystemSession session,
62                                           Collection<? extends Artifact> artifacts,
63                                           Collection<? extends Metadata> metadatas )
64      {
65          final int depth = ConfigUtils.getInteger( session, 2, CONFIG_PROP_DEPTH );
66          if ( depth < 0 || depth > 4 )
67          {
68              throw new IllegalArgumentException( "allowed depth value is between 0 and 4 (inclusive)" );
69          }
70          return delegate.nameLocks( session, artifacts, metadatas ).stream()
71                  .map( n -> hashName( n, depth ) )
72                  .collect( Collectors.toList() );
73      }
74  
75      private String hashName( final String name, final int depth )
76      {
77          String hashedName = StringDigestUtil.sha1( name );
78          if ( depth == 0 )
79          {
80              return hashedName;
81          }
82          StringBuilder prefix = new StringBuilder( "" );
83          int i = 0;
84          while ( i < hashedName.length() && i / 2 < depth )
85          {
86              prefix.append( hashedName, i, i + 2 ).append( "/" );
87              i += 2;
88          }
89          return prefix.append( hashedName ).toString();
90      }
91  }