org.pentaho.platform.osgi.ServerSocketBasedKarafInstanceResolver.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.platform.osgi.ServerSocketBasedKarafInstanceResolver.java

Source

/*
 * This program is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
 * Foundation.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 * or from the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * Copyright 2016 Pentaho Corporation. All rights reserved.
 */
package org.pentaho.platform.osgi;

import org.apache.commons.lang.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.ConnectException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.channels.FileLock;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This implementation resolves Karaf instance numbers based on a ServerSocket port strategy.
 * <p/>
 * It also handles assigning a cache folder appropriate for the client type (spoon, kitchen, carte, etc) guaranteed to
 * not be in use by another instance.
 * <p/>
 * Created by nbaker on 3/21/16.
 */
class ServerSocketBasedKarafInstanceResolver implements IKarafInstanceResolver {
    private static final int START_PORT_NUMBER = determineStartPort();
    public static final String PENTAHO_KARAF_INSTANCE_START_PORT = "pentaho.karaf.instance.start.port";

    static int determineStartPort() {
        String portStr = System.getProperty(PENTAHO_KARAF_INSTANCE_START_PORT, "11000");
        int portNo = NumberUtils.toInt(portStr);
        return portNo != 0 ? portNo : 11000;
    }

    private static final int MAX_NUMBER_OF_KARAF_INSTANCES = 1000;
    public static final String DATA = "data";
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public void resolveInstance(KarafInstance instance) throws KarafInstanceResolverException {

        // Obtaining a valid instance number in and of itself isn't sufficient. Since ports will be assigned based on
        // the instance number as an offset all ports must resolve as well otherwise the instance isn't valid and another
        // should be tried.
        int latestOffsetTried = 0;
        do {
            latestOffsetTried = resolveInstanceNumber(instance, latestOffsetTried);
        } while (!resolvePorts(instance));

        // If no exception was thrown we're here now with all ports resolved
        assignAvailableCacheFolderForType(instance);

    }

    private void assignAvailableCacheFolderForType(KarafInstance instance) {

        // something like karaf/caches
        String cacheParentFolder = instance.getCacheParentFolder();

        // We separate the caches by client type to avoid reuse of an inappropriate data folder
        File clientTypeCacheFolder = new File(cacheParentFolder + "/" + instance.getClientType());
        clientTypeCacheFolder.mkdirs();

        File[] dataDirectories = clientTypeCacheFolder.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.startsWith(DATA);
            }
        });

        int maxInstanceNoFound = 0;
        Pattern pattern = Pattern.compile(DATA + "\\-([0-9]+)");

        // Go through existing data directories. If one is not in-use, use that. If not find the highest number and create
        // one greater
        for (File dataDirectory : dataDirectories) {
            boolean locked = true;
            Matcher matcher = pattern.matcher(dataDirectory.getName());
            if (!matcher.matches()) {
                // unexpected directory, not a data folder, skipping
                continue;
            }
            // extract the data folder number
            int instanceNo = Integer.parseInt(matcher.group(1));

            maxInstanceNoFound = Math.max(maxInstanceNoFound, instanceNo);

            File lockFile = new File(dataDirectory, ".lock");
            FileLock lock = null;
            if (!lockFile.exists()) {
                // Lock file was not present, we can use it
                locked = false;
            } else {
                // Lock file was there, see if another process actually holds a lock on it
                try {
                    FileOutputStream fileOutputStream = new FileOutputStream(lockFile);
                    try {
                        lock = fileOutputStream.getChannel().tryLock();
                        if (lock != null) {
                            // not locked by another program
                            instance.setCacheLock(lock);
                            locked = false;
                        }
                    } catch (Exception e) {
                        // Lock active on another program
                    }
                } catch (FileNotFoundException e) {
                    logger.error("Error locking file in data cache directory", e);
                }
            }

            if (!locked) {
                instance.setCachePath(dataDirectory.getPath());
                // we're good to use this one, break out of existing directory loop
                break;
            }
        }

        if (instance.getCachePath() == null) {
            // Create a new cache folder
            File newCacheFolder = null;
            while (newCacheFolder == null) {
                maxInstanceNoFound++;
                File candidate = new File(clientTypeCacheFolder, DATA + "-" + maxInstanceNoFound);
                if (candidate.exists()) {
                    // Another process slipped in and created a folder, lets skip over them
                    continue;
                }
                newCacheFolder = candidate;
            }

            FileOutputStream fileOutputStream = null;
            try {
                newCacheFolder.mkdir();
                // create lock file and lock it for this process

                File lockFile = new File(newCacheFolder, ".lock");
                fileOutputStream = new FileOutputStream(lockFile);
                FileLock lock = fileOutputStream.getChannel().lock();
                instance.setCachePath(newCacheFolder.getPath());
                instance.setCacheLock(lock);
            } catch (IOException e) {
                logger.error("Error creating data cache folder", e);
            }
        }

    }

    private boolean resolvePorts(KarafInstance instance) {
        List<KarafInstancePort> ports = instance.getPorts();
        int instanceNumber = instance.getInstanceNumber();
        for (KarafInstancePort port : ports) {
            int portNo = port.getStartPort() + instanceNumber;
            if (!isPortAvailable(portNo)) {
                return false;
            }
            port.setAssignedPort(portNo);
        }

        return true;
    }

    private boolean isPortAvailable(int port) {

        try (Socket ignored = new Socket("localhost", port)) {
            return false;
        } catch (IOException e) {
            return true;
        }
    }

    private int resolveInstanceNumber(KarafInstance instance, int latestOffsetTried)
            throws KarafInstanceResolverException {
        logger.debug("Attempting to resolve available Karaf instance number by way of Server Socket");
        int testInstance = latestOffsetTried + 1;
        Integer instanceNo = null;
        do {

            int candidate = START_PORT_NUMBER + testInstance;
            Socket socket = null;
            try {
                logger.debug("Instance test. Trying port " + candidate);
                socket = new Socket("localhost", candidate);
                socket.close();
            } catch (ConnectException e) {
                // port not in-use

                try {
                    ServerSocket ssocket = new ServerSocket(candidate);
                    instanceNo = testInstance;
                    instance.setInstanceSocket(ssocket);
                    instance.setInstanceNumber(instanceNo);
                    logger.debug("Karaf instance resolved to: " + instanceNo);
                } catch (IOException e1) {
                    logger.error("Error creating ServerSocket", e1);
                }

            } catch (IOException ignored) {
                // Some other error, move to next candidate
            }
        } while (instanceNo == null && testInstance++ <= MAX_NUMBER_OF_KARAF_INSTANCES);

        if (instanceNo == null) {
            throw new KarafInstanceResolverException("Unable to resolve Karaf Instance number");
        }
        return instanceNo;
    }
}