This is an automated email from the ASF dual-hosted git repository.

michaelo pushed a commit to branch MRESOLVER-131
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git

commit d045b4d4b80971b468230b45e8bf09fc6c9cc422
Author: Michael Osipov <micha...@apache.org>
AuthorDate: Sun Aug 9 20:05:53 2020 +0200

    [MRESOLVER-131] Introduce a Redisson-based SyncContextFactory
    
    This closes #68
---
 maven-resolver-synccontext-redisson/pom.xml        | 100 ++++++
 .../aether/internal/impl/TrackingFileManager.java  | 151 ++++++++
 .../synccontext/RedissonSyncContextFactory.java    | 390 +++++++++++++++++++++
 .../src/site/markdown/index.md.vm                  | 105 ++++++
 .../src/site/site.xml                              |  37 ++
 pom.xml                                            |   1 +
 6 files changed, 784 insertions(+)

diff --git a/maven-resolver-synccontext-redisson/pom.xml 
b/maven-resolver-synccontext-redisson/pom.xml
new file mode 100644
index 0000000..bc069ac
--- /dev/null
+++ b/maven-resolver-synccontext-redisson/pom.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.maven.resolver</groupId>
+    <artifactId>maven-resolver</artifactId>
+    <version>1.5.1-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>maven-resolver-synccontext-redisson</artifactId>
+
+  <name>Maven Artifact Resolver Sync Context Redisson</name>
+  <description>
+      A synchronization context implementation using Redisson distributed 
locks.
+  </description>
+
+  <properties>
+    <javaVersion>8</javaVersion>
+    
<Automatic-Module-Name>org.apache.maven.resolver.synccontext.redisson</Automatic-Module-Name>
+    <Bundle-SymbolicName>${Automatic-Module-Name}</Bundle-SymbolicName>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.maven.resolver</groupId>
+      <artifactId>maven-resolver-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.resolver</groupId>
+      <artifactId>maven-resolver-impl</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.resolver</groupId>
+      <artifactId>maven-resolver-util</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>javax.inject</groupId>
+      <artifactId>javax.inject</artifactId>
+      <scope>provided</scope>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+    <groupId>javax.annotation</groupId>
+      <artifactId>javax.annotation-api</artifactId>
+      <version>1.3.2</version>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.redisson</groupId>
+      <artifactId>redisson</artifactId>
+      <version>3.13.3</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.eclipse.sisu</groupId>
+        <artifactId>sisu-maven-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            
<manifestFile>${project.build.directory}/osgi/MANIFEST.MF</manifestFile>
+          </archive>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git 
a/maven-resolver-synccontext-redisson/src/main/java/org/eclipse/aether/internal/impl/TrackingFileManager.java
 
b/maven-resolver-synccontext-redisson/src/main/java/org/eclipse/aether/internal/impl/TrackingFileManager.java
new file mode 100644
index 0000000..ffc200f
--- /dev/null
+++ 
b/maven-resolver-synccontext-redisson/src/main/java/org/eclipse/aether/internal/impl/TrackingFileManager.java
@@ -0,0 +1,151 @@
+package org.eclipse.aether.internal.impl;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.eclipse.aether.SyncContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Manages access to a properties file. This override drops internal 
synchronization becauses it
+ * relies on external synchronization provided by outer {@link SyncContext} 
instances.
+ */
+class TrackingFileManager
+{
+
+    private static final Logger LOGGER = LoggerFactory.getLogger( 
TrackingFileManager.class );
+
+    public Properties read( File file )
+    {
+        FileInputStream stream = null;
+        try
+        {
+            if ( !file.exists() )
+            {
+                return null;
+            }
+
+            stream = new FileInputStream( file );
+
+            Properties props = new Properties();
+            props.load( stream );
+
+            return props;
+        }
+        catch ( IOException e )
+        {
+            LOGGER.warn( "Failed to read tracking file {}", file, e );
+        }
+        finally
+        {
+            close( stream, file );
+        }
+
+        return null;
+    }
+
+    public Properties update( File file, Map<String, String> updates )
+    {
+        Properties props = new Properties();
+
+        File directory = file.getParentFile();
+        if ( !directory.mkdirs() && !directory.exists() )
+        {
+            LOGGER.warn( "Failed to create parent directories for tracking 
file {}", file );
+            return props;
+        }
+
+        RandomAccessFile raf = null;
+        try
+        {
+            raf = new RandomAccessFile( file, "rw" );
+
+            if ( file.canRead() )
+            {
+                byte[] buffer = new byte[(int) raf.length()];
+
+                raf.readFully( buffer );
+
+                ByteArrayInputStream stream = new ByteArrayInputStream( buffer 
);
+
+                props.load( stream );
+            }
+
+            for ( Map.Entry<String, String> update : updates.entrySet() )
+            {
+                if ( update.getValue() == null )
+                {
+                    props.remove( update.getKey() );
+                }
+                else
+                {
+                    props.setProperty( update.getKey(), update.getValue() );
+                }
+            }
+
+            ByteArrayOutputStream stream = new ByteArrayOutputStream( 1024 * 2 
);
+
+            LOGGER.debug( "Writing tracking file {}", file );
+            props.store( stream, "NOTE: This is a Maven Resolver internal 
implementation file"
+                + ", its format can be changed without prior notice." );
+
+            raf.seek( 0 );
+            raf.write( stream.toByteArray() );
+            raf.setLength( raf.getFilePointer() );
+        }
+        catch ( IOException e )
+        {
+            LOGGER.warn( "Failed to write tracking file {}", file, e );
+        }
+        finally
+        {
+            close( raf, file );
+        }
+
+        return props;
+    }
+
+    private void close( Closeable closeable, File file )
+    {
+        if ( closeable != null )
+        {
+            try
+            {
+                closeable.close();
+            }
+            catch ( IOException e )
+            {
+                LOGGER.warn( "Error closing tracking file {}", file, e );
+            }
+        }
+    }
+
+}
diff --git 
a/maven-resolver-synccontext-redisson/src/main/java/org/eclipse/aether/synccontext/RedissonSyncContextFactory.java
 
b/maven-resolver-synccontext-redisson/src/main/java/org/eclipse/aether/synccontext/RedissonSyncContextFactory.java
new file mode 100644
index 0000000..4bdcf75
--- /dev/null
+++ 
b/maven-resolver-synccontext-redisson/src/main/java/org/eclipse/aether/synccontext/RedissonSyncContextFactory.java
@@ -0,0 +1,390 @@
+package org.eclipse.aether.synccontext;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.TreeSet;
+
+import javax.annotation.PreDestroy;
+import javax.annotation.Priority;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.SyncContext;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.impl.SyncContextFactory;
+import org.eclipse.aether.metadata.Metadata;
+import org.eclipse.aether.util.ChecksumUtils;
+import org.eclipse.aether.util.ConfigUtils;
+import org.redisson.Redisson;
+import org.redisson.api.RLock;
+import org.redisson.api.RReadWriteLock;
+import org.redisson.api.RedissonClient;
+import org.redisson.config.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A singleton factory to create synchronization contexts using Redisson's 
{@link RReadWriteLock}.
+ * It locks fine-grained with groupId, artifactId and version if required.
+ * <p>
+ * <strong>Note: This component is still considered to be experimental, use 
with caution!</strong>
+ * <h2>Configuration</h2>
+ * You can configure various aspects of this factory.
+ *
+ * <h3>Redisson Client</h3>
+ * To fully configure the Redisson client, this factory uses the following 
staggered approach:
+ * <ol>
+ * <li>If the property {@code aether.syncContext.redisson.configFile} is set 
and the file at that
+ * specific path does exist, load it otherwise an exception is thrown.</li>
+ * <li>If no configuration file path is provided, load default from
+ * <code>${maven.conf}/maven-resolver-redisson.yaml</code>, but ignore if it 
does not exist.</li>
+ * <li>If no configuration file is available at all, Redisson is configured 
with a single server pointing
+ * to {@code redis://localhost:6379} with client name {@code 
maven-resolver}.</li>
+ * </ol>
+ * Please note that an invalid confguration file results in an exception too.
+ *
+ * <h3>Discrimination</h3>
+ * You may freely use a single Redis instance to serve multiple Maven 
instances, on multiple hosts
+ * with shared or exclusive local repositories. Every sync context instance 
will generate a unique
+ * discriminator which identifies each host paired with the local repository 
currently accessed.
+ * The following staggered approach is used:
+ * <ol>
+ * <li>Determine hostname, if not possible use {@code localhost}.</li>
+ * <li>If the property {@code aether.syncContext.redisson.discriminator} is 
set, use it and skip
+ * the remaining steps.</li>
+ * <li>Concat hostname with the path of the local repository: 
<code>${hostname}:${maven.repo.local}</code>.</li>
+ * <li>Calculate the SHA-1 digest of this value. If that fails use the static 
digest of an empty string.</li>
+ * </ol>
+ *
+ * <h2>Key Composition</h2>
+ * Each lock is assigned a unique key in the configured Redis instance which 
has the following pattern:
+ * <code>maven:resolver:${discriminator}:${artifact|metadata}</code>.
+ * <ul>
+ * <li><code>${artifact}</code> will
+ * always resolve to 
<code>artifact:${groupId}:${artifactId}:${baseVersion}</code>.</li>
+ * <li><code>${metadata}</code> will resolve to one of 
<code>metadata:${groupId}:${artifactId}:${version}</code>,
+ * <code>metadata:${groupId}:${artifactId}</code>, 
<code>metadata:${groupId}</code>,
+ * <code>metadata:</code>.</li>
+ * </ul>
+ */
+@Named
+@Priority( Integer.MAX_VALUE )
+@Singleton
+public class RedissonSyncContextFactory
+    implements SyncContextFactory
+{
+
+    private static final String DEFAULT_CONFIG_FILE_NAME = 
"maven-resolver-redisson.yaml";
+    private static final String DEFAULT_REDIS_ADDRESS = 
"redis://localhost:6379";
+    private static final String DEFAULT_CLIENT_NAME = "maven-resolver";
+    private static final String DEFAULT_HOSTNAME = "localhost";
+    private static final String DEFAULT_DISCRIMINATOR_DIGEST = 
"da39a3ee5e6b4b0d3255bfef95601890afd80709";
+
+    private static final String CONFIG_PROP_CONFIG_FILE = 
"aether.syncContext.redisson.configFile";
+
+    private static final Logger LOGGER = LoggerFactory.getLogger( 
RedissonSyncContextFactory.class );
+
+    // We are in a singleton so these should exist only once!
+    private RedissonClient redissonClient;
+    private String hostname;
+
+    public RedissonSyncContextFactory()
+    {
+        // TODO These two log statements will go away
+        LOGGER.trace( "TCCL: {}", 
Thread.currentThread().getContextClassLoader() );
+        LOGGER.trace( "CCL: {}", getClass().getClassLoader() );
+        this.redissonClient = createRedissonClient();
+        this.hostname = getHostname();
+    }
+
+    private RedissonClient createRedissonClient()
+    {
+        Path configFilePath = null;
+
+        String configFile = ConfigUtils.getString( System.getProperties(), 
null, CONFIG_PROP_CONFIG_FILE );
+        if ( configFile != null && !configFile.isEmpty() )
+        {
+            configFilePath = Paths.get( configFile );
+            if ( Files.notExists( configFilePath ) )
+            {
+                throw new IllegalArgumentException( "The specified Redisson 
config file does not exist: "
+                                                    + configFilePath );
+            }
+        }
+
+        if ( configFilePath == null )
+        {
+            String mavenConf = ConfigUtils.getString( System.getProperties(), 
null, "maven.conf" );
+            if ( mavenConf != null && !mavenConf.isEmpty() )
+            {
+                configFilePath = Paths.get( mavenConf, 
DEFAULT_CONFIG_FILE_NAME );
+                if ( Files.notExists( configFilePath ) )
+                {
+                    configFilePath = null;
+                }
+            }
+        }
+
+        Config config = null;
+
+        if ( configFilePath != null )
+        {
+            LOGGER.trace( "Reading Redisson config file from '{}'", 
configFilePath );
+            try ( InputStream is = Files.newInputStream( configFilePath ) )
+            {
+                config = Config.fromYAML( is );
+            }
+            catch ( IOException e )
+            {
+                throw new IllegalStateException( "Failed to read Redisson 
config file: " + configFilePath, e );
+            }
+        }
+        else
+        {
+            config = new Config();
+            config.useSingleServer()
+                .setAddress( DEFAULT_REDIS_ADDRESS )
+                .setClientName( DEFAULT_CLIENT_NAME );
+        }
+
+        RedissonClient redissonClient = Redisson.create( config );
+        LOGGER.trace( "Created Redisson client with id '{}'", 
redissonClient.getId() );
+
+        return redissonClient;
+    }
+
+    private String getHostname()
+    {
+        try
+        {
+            return InetAddress.getLocalHost().getHostName();
+        }
+        catch ( UnknownHostException e )
+        {
+            LOGGER.warn( "Failed to get hostname, using '{}'",
+                         DEFAULT_HOSTNAME, e );
+            return DEFAULT_HOSTNAME;
+        }
+    }
+
+    public SyncContext newInstance( RepositorySystemSession session, boolean 
shared )
+    {
+        // This log statement will go away
+        LOGGER.trace( "Instance: {}", this );
+        return new RedissonSyncContext( session, hostname, redissonClient, 
shared );
+    }
+
+    @PreDestroy
+    public void shutdown()
+    {
+        LOGGER.trace( "Shutting down Redisson client with id '{}'", 
redissonClient.getId() );
+        redissonClient.shutdown();
+    }
+
+    static class RedissonSyncContext
+        implements SyncContext
+    {
+
+        private static final String CONFIG_PROP_DISCRIMINATOR = 
"aether.syncContext.redisson.discriminator";
+
+        private static final String KEY_PREFIX = "maven:resolver:";
+
+        private static final Logger LOGGER = LoggerFactory.getLogger( 
RedissonSyncContext.class );
+
+        private final RepositorySystemSession session;
+        private final String hostname;
+        private final RedissonClient redissonClient;
+        private final boolean shared;
+        private final Map<String, RReadWriteLock> locks = new 
LinkedHashMap<>();
+
+        private RedissonSyncContext( RepositorySystemSession session, String 
hostname,
+                RedissonClient redissonClient, boolean shared )
+        {
+            this.session = session;
+            this.hostname = hostname;
+            this.redissonClient = redissonClient;
+            this.shared = shared;
+        }
+
+        public void acquire( Collection<? extends Artifact> artifacts,
+                Collection<? extends Metadata> metadatas )
+        {
+            // Deadlock prevention: https://stackoverflow.com/a/16780988/696632
+            // We must acquire multiple locks always in the same order!
+            Collection<String> keys = new TreeSet<>();
+            if ( artifacts != null )
+            {
+                for ( Artifact artifact : artifacts )
+                {
+                    // TODO Should we include extension and classifier too?
+                    String key = "artifact:" + artifact.getGroupId() + ":"
+                            + artifact.getArtifactId() + ":" + 
artifact.getBaseVersion();
+                    keys.add( key );
+                }
+            }
+
+            if ( metadatas != null )
+            {
+                for ( Metadata metadata : metadatas )
+                {
+                    StringBuilder key = new StringBuilder( "metadata:" );
+                    if ( !metadata.getGroupId().isEmpty() )
+                    {
+                        key.append( metadata.getGroupId() );
+                        if ( !metadata.getArtifactId().isEmpty() )
+                        {
+                            key.append( ':' ).append( metadata.getArtifactId() 
);
+                            if ( !metadata.getVersion().isEmpty() )
+                            {
+                                key.append( ':' ).append( 
metadata.getVersion() );
+                            }
+                        }
+                    }
+                    keys.add( key.toString() );
+                }
+            }
+
+            if ( keys.isEmpty() )
+            {
+                return;
+            }
+
+            String discriminator = createDiscriminator();
+            LOGGER.trace( "Using Redis key discriminator '{}' during this 
session", discriminator );
+
+            LOGGER.trace( "Need {} {} lock(s) for {}", keys.size(), shared ? 
"read" : "write", keys );
+            int acquiredLockCount = 0;
+            int reacquiredLockCount = 0;
+            for ( String key : keys )
+            {
+                RReadWriteLock rwLock = locks.get( key );
+                if ( rwLock == null )
+                {
+                    rwLock = redissonClient
+                            .getReadWriteLock( KEY_PREFIX + discriminator + 
":" + key );
+                    locks.put( key, rwLock );
+                    acquiredLockCount++;
+                }
+                else
+                {
+                    reacquiredLockCount++;
+                }
+
+                RLock actualLock = shared ? rwLock.readLock() : 
rwLock.writeLock();
+                // Avoid #getHoldCount() and #isLocked() roundtrips when we 
are not logging
+                if ( LOGGER.isTraceEnabled() )
+                {
+                    LOGGER.trace( "Acquiring {} lock for '{}' (currently held: 
{}, already locked: {})",
+                                  shared ? "read" : "write", key, 
actualLock.getHoldCount(),
+                                  actualLock.isLocked() );
+                }
+                // If this still produces a deadlock we might need to switch 
to #tryLock() with n attempts
+                actualLock.lock();
+            }
+            LOGGER.trace( "Total new locks acquired: {}, total existing locks 
reacquired: {}",
+                          acquiredLockCount, reacquiredLockCount );
+        }
+
+        private String createDiscriminator()
+        {
+            String discriminator = ConfigUtils.getString( session, null, 
CONFIG_PROP_DISCRIMINATOR );
+
+            if ( discriminator == null || discriminator.isEmpty() )
+            {
+
+                File basedir = session.getLocalRepository().getBasedir();
+                discriminator = hostname + ":" + basedir;
+                try
+                {
+                    Map<String, Object> checksums = ChecksumUtils.calc(
+                            discriminator.toString().getBytes( 
StandardCharsets.UTF_8 ),
+                            Collections.singletonList( "SHA-1" ) );
+                    Object checksum = checksums.get( "SHA-1" );
+
+                    if ( checksum instanceof Exception )
+                    {
+                        throw (Exception) checksum;
+                    }
+
+                    return String.valueOf( checksum );
+                }
+                catch ( Exception e )
+                {
+                    // TODO Should this be warn?
+                    LOGGER.trace( "Failed to calculate discriminator digest, 
using '{}'",
+                                  DEFAULT_DISCRIMINATOR_DIGEST, e );
+                    return DEFAULT_DISCRIMINATOR_DIGEST;
+                }
+            }
+
+            return discriminator;
+        }
+
+        public void close()
+        {
+            if ( locks.isEmpty() )
+            {
+                return;
+            }
+
+            // Release locks in reverse insertion order
+            Deque<String> keys = new LinkedList<>( locks.keySet() );
+            Iterator<String> keysIter = keys.descendingIterator();
+            while ( keysIter.hasNext() )
+            {
+                String key = keysIter.next();
+                RReadWriteLock rwLock = locks.get( key );
+                RLock actualLock = shared ? rwLock.readLock() : 
rwLock.writeLock();
+                while ( actualLock.getHoldCount() > 0 )
+                {
+                    // Avoid #getHoldCount() roundtrips when we are not logging
+                    if ( LOGGER.isTraceEnabled() )
+                    {
+                        LOGGER.trace( "Releasing {} lock for '{}' (currently 
held: {})",
+                                      shared ? "read" : "write", key, 
actualLock.getHoldCount() );
+                    }
+                    actualLock.unlock();
+                }
+            }
+            // TODO Should we count reentrant ones too?
+            LOGGER.trace( "Total locks released: {}", locks.size() );
+            locks.clear();
+        }
+
+    }
+
+}
diff --git a/maven-resolver-synccontext-redisson/src/site/markdown/index.md.vm 
b/maven-resolver-synccontext-redisson/src/site/markdown/index.md.vm
new file mode 100644
index 0000000..7f1f004
--- /dev/null
+++ b/maven-resolver-synccontext-redisson/src/site/markdown/index.md.vm
@@ -0,0 +1,105 @@
+${esc.hash} Redisson Sync Context for Maven Resolver
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<span style="color: red; font-size: 16pt">***Note***: *This component is still 
considered to be experimental, use with caution!*</span>
+
+The Redisson Sync Context Factory is a Redisson-based distributed locks 
factory for Maven Resolver
+on top of Redis to provide a fast, concurrent-safe access from one or multiple 
Maven instances to the
+same local Maven repository.
+
+For further details about the factory read the 
[Javadoc](./apidocs/org/eclipse/aether/synccontext/RedissonSyncContextFactory.html).
+
+${esc.hash}${esc.hash} Open Issues/Notes
+
+- It only works when dependency injection is used and not the bundled 
`AetherModule` or
+  `ServiceLocator` (Maven uses dependency injection).
+- It includes a lot of trace logging which partially will go way as soon as it 
has been stabilized.
+- Usage from plugins has not been tested yet.
+- The `furnace-maven-plugin` does not work this implementation because it uses 
`ServiceLocator` instead
+  of dependency injection.
+- The installation process has not been streamlined yet.
+
+${esc.hash}${esc.hash} Installation/Testing
+
+- Clone Maven Resolver and perform `mvn install`.
+- Clone Maven, change the Resolver version in Maven's parent POM to 
`${project.version}` and perform `mvn install`.
+- Extract `apache-maven-3.7.0-SNAPSHOT-bin.tar.gz` to a location of your 
choice.
+- Modify `${maven.home}/bin/m2.conf` by adding `optionally 
${maven.home}/lib/ext/redisson/*.jar`
+  right after the `${maven.home}/conf/logging` line.
+- Add/modify the following entries in 
`${maven.home}/conf/logging/simplelogger.properties`:
+      ```
+      org.slf4j.simpleLogger.showDateTime=true
+      org.slf4j.simpleLogger.showThreadName=true
+      org.slf4j.simpleLogger.showShortLogName=true
+      org.slf4j.simpleLogger.log.org.eclipse.aether=trace
+      #org.slf4j.simpleLogger.log.org.redisson=debug
+      #org.slf4j.simpleLogger.log.io.netty=debug
+      ```
+- Go back to Resolver and run `mvn dependency:copy-dependencies -pl 
maven-resolver-synccontext-redisson`.
+- Copy the following dependencies from 
`maven-resolver-synccontext-redisson/target/dependency`
+  to `${maven.home}/lib/ext/redisson/`:
+      ```
+      ├── byte-buddy-1.10.7.jar
+      ├── cache-api-1.0.0.jar
+      ├── jackson-annotations-2.11.1.jar
+      ├── jackson-core-2.11.1.jar
+      ├── jackson-databind-2.11.1.jar
+      ├── jackson-dataformat-yaml-2.11.1.jar
+      ├── javax.annotation-api-1.3.2.jar
+      ├── jboss-marshalling-2.0.9.Final.jar
+      ├── jboss-marshalling-river-2.0.9.Final.jar
+      ├── jodd-bean-5.0.13.jar
+      ├── jodd-core-5.0.13.jar
+      ├── maven-resolver-synccontext-redisson-${project.version}.jar
+      ├── netty-buffer-4.1.51.Final.jar
+      ├── netty-codec-4.1.51.Final.jar
+      ├── netty-codec-dns-4.1.51.Final.jar
+      ├── netty-common-4.1.51.Final.jar
+      ├── netty-handler-4.1.51.Final.jar
+      ├── netty-resolver-4.1.51.Final.jar
+      ├── netty-resolver-dns-4.1.51.Final.jar
+      ├── netty-transport-4.1.51.Final.jar
+      ├── reactive-streams-1.0.3.jar
+      ├── reactor-core-3.3.4.RELEASE.jar
+      ├── redisson-3.13.3.jar
+      ├── rxjava-2.2.19.jar
+      └── snakeyaml-1.26.jar
+      ```
+    Dependencies which are already bundled with Maven have been omitted.
+- Start your Redis instance on a machine of your choice (ideally `localhost`).
+- Now start a multithreaded Maven (`3.7.0-SNAPSHOT`) build on your project and 
you should see at least these lines:
+      ```
+      # This line does not appear for the default configuration
+      2316 [main] [TRACE] RedissonSyncContextFactory - Reading Redisson config 
file from '${maven.home}/conf/maven-resolver-redisson.yaml'
+      4626 [main] [TRACE] RedissonSyncContextFactory - Created Redisson client 
with id '1c8db59b-7939-4014-8506-ae841c74608c'
+      35318 [main] [TRACE] RedissonSyncContextFactory - Shutting down Redisson 
client with id '1c8db59b-7939-4014-8506-ae841c74608c'
+      ```
+
+${esc.hash}${esc.hash} Configuration Options
+
+Option | Type | Description | Default Value
+--- | --- | --- | --- | ---
+`aether.syncContext.redisson.configFile` | String | Path to a Redisson 
configuration file in YAML format. Read [official 
documentation](https://github.com/redisson/redisson/wiki/2.-Configuration) for 
details. | `${maven.home}/conf/maven-resolver-redisson.yaml`
+`aether.syncContext.redisson.discriminator` | String | A discriminator 
uniquely identifying a host and repository pair. If the generation of the 
default value fails, it will use `sha1('')`. | 
`sha1('${esc.dollar}{hostname:-localhost}:${maven.repo.local}')`
+
+${esc.hash}${esc.hash} Set Configuration from Apache Maven
+
+To set one of the configuration options from above just use system variables.
diff --git a/maven-resolver-synccontext-redisson/src/site/site.xml 
b/maven-resolver-synccontext-redisson/src/site/site.xml
new file mode 100644
index 0000000..0dd2b23
--- /dev/null
+++ b/maven-resolver-synccontext-redisson/src/site/site.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<project xmlns="http://maven.apache.org/DECORATION/1.0.0";
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+  xsi:schemaLocation="http://maven.apache.org/DECORATION/1.0.0 
http://maven.apache.org/xsd/decoration-1.0.0.xsd";
+  name="RedissonSyncContext">
+  <body>
+    <menu name="Overview">
+      <item name="Introduction" href="index.html"/>
+      <item name="Javadoc" href="apidocs/index.html"/>
+      <item name="Source Xref" href="xref/index.html"/>
+      <!--item name="FAQ" href="faq.html"/-->
+    </menu>
+
+    <menu ref="parent"/>
+    <menu ref="reports"/>
+  </body>
+</project>
diff --git a/pom.xml b/pom.xml
index 39bddc7..4d70f4a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -517,6 +517,7 @@
       </activation>
       <modules>
         <module>maven-resolver-synccontext-global</module>
+        <module>maven-resolver-synccontext-redisson</module>
       </modules>
     </profile>
   </profiles>

Reply via email to