org.bitstrings.maven.plugins.portallocator.PortAllocatorMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.bitstrings.maven.plugins.portallocator.PortAllocatorMojo.java

Source

/*
 * 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.
 */
package org.bitstrings.maven.plugins.portallocator;

import static com.google.common.base.MoreObjects.*;
import static com.google.common.base.Strings.*;
import static java.util.Collections.*;
import static org.apache.commons.lang3.BooleanUtils.*;
import static org.apache.maven.plugins.annotations.LifecyclePhase.*;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.lang3.StringUtils;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.bitstrings.maven.plugins.portallocator.util.Helpers;

import com.google.common.base.Function;
import com.google.common.collect.Iterators;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;

@Mojo(name = "allocate", defaultPhase = GENERATE_TEST_RESOURCES, threadSafe = true, requiresProject = true, requiresOnline = false)
public class PortAllocatorMojo extends AbstractMojo {
    @Parameter(defaultValue = "${project}", readonly = true)
    private MavenProject mavenProject;

    @Parameter(defaultValue = "${session}", readonly = true)
    private MavenSession mavenSession;

    @Parameter(defaultValue = "false")
    private boolean quiet;

    @Parameter
    private PortAllocators portAllocators;

    @Parameter
    private Ports ports;

    @Parameter(defaultValue = PORT_NAME_SUFFIX_DEFAULT)
    private String portNameSuffix;

    @Parameter(defaultValue = OFFSET_NAME_SUFFIX_DEFAULT)
    private String offsetNameSuffix;

    @Parameter(defaultValue = NAME_SEPARATOR_DEFAULT)
    private String nameSeparator;

    @Parameter
    private File writePropertiesFile;

    private final Map<String, Integer> executionPortMap = new HashMap<>();

    private static final String PREFERRED_PORTS_DEFAULT = "8090";
    private static final String PORT_NAME_SUFFIX_DEFAULT = "port";
    private static final String OFFSET_NAME_SUFFIX_DEFAULT = "port-offset";
    private static final String NAME_SEPARATOR_DEFAULT = ".";
    private static final String PORT_ALLOCATOR_DEFAULT_ID = "default";
    private static final String DEPLETED_ACTION_DEFAULT = "CONTINUE";

    private static final Set<Integer> ALLOCATED_PORTS = synchronizedSet(new HashSet<Integer>());
    private static final Map<String, PortAllocatorService> PORT_ALLOCATOR_SERVICE_MAP = synchronizedMap(
            new HashMap<String, PortAllocatorService>());

    private static final ReentrantLock ALLOCATION_LOCK = new ReentrantLock();

    private static final PortAllocatorService PORT_ALLOCATOR_SERVICE_DEFAULT;

    static {
        PORT_ALLOCATOR_SERVICE_MAP.put(PORT_ALLOCATOR_DEFAULT_ID,
                PORT_ALLOCATOR_SERVICE_DEFAULT = createPortAllocatorService(
                        initPortAllocator(new PortAllocator())));
    }

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        try {
            if (portAllocators != null) {
                for (PortAllocator portAllocator : portAllocators) {
                    initPortAllocator(portAllocator);

                    final PortAllocatorService existingPas = PORT_ALLOCATOR_SERVICE_MAP.get(portAllocator.getId());

                    if (portAllocator.isPermitOverride() || (existingPas == null)
                            || (portAllocator.getId().equals(PORT_ALLOCATOR_DEFAULT_ID)
                                    && (existingPas == PORT_ALLOCATOR_SERVICE_DEFAULT))) {
                        PORT_ALLOCATOR_SERVICE_MAP.put(portAllocator.getId(),
                                createPortAllocatorService(portAllocator));

                        if (!quiet && getLog().isInfoEnabled()) {
                            getLog().info("Registering port allocator [" + portAllocator.getId() + "]");
                        }
                    }
                }
            }

            if (ports != null) {
                if ((ports.getPortAllocatorRef() != null) && (ports.getPortAllocator() != null)) {
                    throw new MojoExecutionException(
                            "Either use a port allocator reference or define an inner allocator but you can use both.");
                }

                PortAllocatorService pas = ports.getPortAllocator() == null
                        ? PORT_ALLOCATOR_SERVICE_MAP
                                .get(firstNonNull(ports.getPortAllocatorRef(), PORT_ALLOCATOR_DEFAULT_ID))
                        : createPortAllocatorService(initPortAllocator(ports.getPortAllocator()));

                if (pas == null) {
                    throw new MojoExecutionException(
                            "Cannot find port allocator [" + ports.getPortAllocatorRef() + "]");
                }

                // assign
                final LinkedListMultimap<String, Port> portGroupMap = LinkedListMultimap.create();

                for (Port port : ports) {
                    final String offsetFrom = port.getOffsetFrom();
                    final String portGroupName = findGroupRoot(port, portGroupMap);

                    portGroupMap.put(portGroupName, port);

                    if ((offsetFrom != null) && !executionPortMap.containsKey(getPortName(portGroupName))) {
                        throw new MojoExecutionException(
                                "Port [" + port.getName() + "] using offset from undefined [" + offsetFrom + "].");
                    }

                    Iterator<Port> portIterator = Iterators.singletonIterator(port);

                    while (portIterator.hasNext()) {
                        final Port portToAllocate = portIterator.next();

                        final Integer previousPort = executionPortMap.remove(getPortName(portToAllocate.getName()));
                        executionPortMap.remove(getOffsetName(portToAllocate.getName()));

                        if (!allocatePort(pas, portToAllocate)) {
                            if (portToAllocate.getOffsetFrom() != null) {
                                portIterator = portGroupMap.get(portGroupName).listIterator();
                            }
                        }

                        ALLOCATION_LOCK.lock();
                        ALLOCATED_PORTS.remove(previousPort);
                        ALLOCATION_LOCK.unlock();
                    }
                }

                // log ports
                for (Port port : ports) {
                    if (!quiet && getLog().isInfoEnabled()) {
                        String name = getPortName(port.getName());
                        Integer value = executionPortMap.get(name);

                        if (value != null) {
                            getLog().info("Assigning port [" + value + "] to property [" + name + "]");
                        }

                        name = getOffsetName(port.getName());
                        value = executionPortMap.get(name);

                        if (value != null) {
                            getLog().info("Assigning offset [" + value + "] " + "using preferred port ["
                                    + port.getPreferredPort() + "] " + "to property [" + name + "]");
                        }
                    }
                }
            }

            if (writePropertiesFile != null) {
                final File parent = writePropertiesFile.getParentFile();

                if ((parent != null) && !parent.exists()) {
                    parent.mkdirs();
                }

                try (final Writer out = new BufferedWriter(new FileWriter(writePropertiesFile))) {
                    if (!quiet && getLog().isInfoEnabled()) {
                        getLog().info("Writing ports file [" + writePropertiesFile + "]");
                    }

                    final Properties outProps = new Properties();
                    outProps.putAll(Maps.transformValues(executionPortMap, new Function<Integer, String>() {
                        @Override
                        public String apply(Integer input) {
                            return input.toString();
                        }
                    }));
                    outProps.store(out, null);
                } catch (Exception e) {
                    throw new MojoExecutionException("Problem writing ports file [" + writePropertiesFile + "]", e);
                }
            }
        } catch (MojoExecutionException e) {
            throw e;
        } catch (Exception e) {
            throw new MojoExecutionException(e.getLocalizedMessage(), e);
        }
    }

    protected static PortAllocatorService createPortAllocatorService(PortAllocator portAllocator) {
        final PortAllocatorService.Builder pasBuilder = new PortAllocatorService.Builder();

        if (portAllocator.getDepletedAction() == PortAllocator.DepletedAction.CONTINUE) {
            pasBuilder.overflowPermitted();
        }

        for (String portsCsv : portAllocator.getPreferredPorts().getPortsList()) {
            addPortRanges(pasBuilder, portsCsv);
        }

        pasBuilder.listener(new PortAllocatorService.Listener() {
            @Override
            public boolean beforeAllocation(int potentialPort) {
                ALLOCATION_LOCK.lock();

                return !ALLOCATED_PORTS.contains(potentialPort);
            }

            @Override
            public void afterAllocation(int port) {
                ALLOCATED_PORTS.add(port);

                ALLOCATION_LOCK.unlock();
            }
        });

        return pasBuilder.build();
    }

    protected static PortAllocator initPortAllocator(PortAllocator portAllocator) {
        if (portAllocator.getDepletedAction() == null) {
            portAllocator.setDepletedAction(DEPLETED_ACTION_DEFAULT);
        }

        if (portAllocator.getPreferredPorts() == null) {
            portAllocator.setPreferredPorts(new PortAllocator.PreferredPorts());
        }

        if (portAllocator.getPreferredPorts().getPortsList().isEmpty()) {
            portAllocator.getPreferredPorts().addPorts(PREFERRED_PORTS_DEFAULT);
        }

        if (isNullOrEmpty(portAllocator.getId())) {
            portAllocator.setId(PORT_ALLOCATOR_DEFAULT_ID);
        }

        return portAllocator;
    }

    protected static void addPortRanges(PortAllocatorService.Builder builder, String portsCsv) {
        for (String portRange : Helpers.iterateOnCsv(portsCsv)) {
            final int rangeSepIndex = portRange.indexOf('-');

            if (rangeSepIndex == -1) {
                builder.port(Integer.parseInt(portRange.trim()));
            } else {
                final int lowest = Integer.parseInt(portRange.substring(0, rangeSepIndex).trim());

                if (rangeSepIndex == portRange.length()) {
                    builder.portFrom(lowest);
                } else {
                    builder.portRange(lowest, Integer.parseInt(portRange.substring(rangeSepIndex + 1).trim()));
                }
            }
        }
    }

    protected boolean allocatePort(PortAllocatorService pas, Port portConfig)
            throws MojoExecutionException, IOException {
        final String portNamePrefix = portConfig.getName();
        final String portPropertyName = getPortName(portNamePrefix);

        final int allocatedPort;

        if (portConfig.getOffsetFrom() != null) {
            if (portConfig.getPreferredPort() == null) {
                throw new MojoExecutionException("'preferredPort' must be set when using 'fromOffset'.");
            }

            final String offsetFromName = getOffsetName(portConfig.getOffsetFrom());
            final Integer fromOffset = executionPortMap.get(offsetFromName);

            if (fromOffset == null) {
                throw new MojoExecutionException("Port [" + portPropertyName
                        + "] references an unknown port offset [" + offsetFromName + "]");
            }

            allocatedPort = (portConfig.getPreferredPort() + fromOffset);

            if (!pas.usePort(allocatedPort)) {
                return false;
            }
        } else {
            allocatedPort = ((portConfig.getPreferredPort() != null) && pas.usePort(portConfig.getPreferredPort()))
                    ? portConfig.getPreferredPort()
                    : pas.nextAvailablePort();

            if (allocatedPort == PortAllocatorService.PORT_NA) {
                throw new MojoExecutionException("Unable to allocate a port for [" + portPropertyName + "]");
            }
        }

        mavenProject.getProperties().put(portPropertyName, String.valueOf(allocatedPort));
        executionPortMap.put(portPropertyName, allocatedPort);

        if (isTrue(portConfig.getSetOffsetProperty() != null)) {
            if (portConfig.getPreferredPort() == null) {
                throw new MojoExecutionException("'preferredPort' must be set when 'setOffsetProperty = true'.");
            }

            final String offsetPropertyName = getOffsetName(portNamePrefix);
            final int offset = (allocatedPort - portConfig.getPreferredPort());

            mavenProject.getProperties().put(offsetPropertyName, String.valueOf(offset));
            executionPortMap.put(offsetPropertyName, offset);
        }

        return true;
    }

    protected String findGroupRoot(Port port, ListMultimap<String, Port> portGroupMap) {
        while ((port != null) && (port.getOffsetFrom() != null) && portGroupMap.containsKey(port.getOffsetFrom())) {
            port = portGroupMap.get(port.getOffsetFrom()).get(0);
        }

        return port.getName();
    }

    protected String getPortName(String prefix) {
        return buildName(prefix, portNameSuffix);
    }

    protected String getOffsetName(String prefix) {
        return buildName(prefix, offsetNameSuffix);
    }

    protected String buildName(String... parts) {
        return StringUtils.join(parts, nameSeparator);
    }
}