/*
* 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 com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang.StringUtils;
import org.pentaho.platform.settings.PortFileManager;
import org.pentaho.platform.settings.ServerPort;
import org.pentaho.platform.settings.ServerPortRegistry;
import org.pentaho.platform.settings.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.nio.channels.FileLock;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* This class assigns and configures property settings for separate karaf instances so that multiple client/server
* applications can run simultaneously on the same host. It assigns/creates a unique cache folder for each karaf
* instance and maintains what folders are in use by implementing a lock file.
*
* @author tkafalas
*/
public class KarafInstance {
private static final String YAML_FILE_NAME = "KarafPorts.yaml";
private static Logger logger = LoggerFactory.getLogger( KarafInstance.class );
private int instanceNumber = 0;
private ServerSocket instanceSocket;
private String cachePath;
private final String cacheParentFolder;
private HashMap<String, KarafInstancePort> instancePorts = new HashMap<String, KarafInstancePort>();
private static final int BANNER_WIDTH = 79;
private static final String USED_PORT_FILENAME = "PortsAssigned.txt";
private boolean started;
private StringBuilder banner = new StringBuilder();
private String instanceFilePath;
private String clientType;
private FileLock cacheLock;
public KarafInstance( String root, String clientType ) {
this( root, System.getProperty( "karaf.etc" ) + "/" + YAML_FILE_NAME, "default" );
this.clientType = clientType;
}
public KarafInstance( String root, String instanceFilePath, String clientType ) {
this.cacheParentFolder = root + "/caches";
this.instanceFilePath = instanceFilePath;
try {
processConfigFile();
} catch ( FileNotFoundException e ) {
throw new IllegalStateException( "Port Config file (" + this.instanceFilePath + ") not found", e );
}
}
public void setCachePath( String cachePath ) {
this.cachePath = cachePath;
System.setProperty( "karaf.data", cachePath );
}
@SuppressWarnings( "unchecked" )
public void processConfigFile() throws FileNotFoundException {
try ( InputStream input = new FileInputStream( new File( instanceFilePath ) ) ) {
Yaml yaml = new Yaml();
Map<String, Object> map = (Map<String, Object>) yaml.load( input );
List<Map<String, Object>> serviceList = (List<Map<String, Object>>) map.get( "Service" );
for ( Map<String, Object> serviceMap : serviceList ) {
Service service =
new Service( serviceMap.get( "serviceName" ).toString(),
serviceMap.get( "serviceDescription" ).toString() );
ServerPortRegistry.addService( service );
}
List<Map<String, Object>> portList = (List<Map<String, Object>>) map.get( "ServerPort" );
for ( Map<String, Object> portMap : portList ) {
KarafInstancePort karafPort =
new KarafInstancePort( this, portMap.get( "id" ).toString(), portMap.get( "property" ).toString(),
portMap.get(
"friendlyName" ).toString(), (Integer) portMap.get( "startPort" ),
portMap.get( "serviceName" ).toString() );
this.registerPort( karafPort );
}
} catch ( FileNotFoundException e ) {
throw new FileNotFoundException( "File " + instanceFilePath
+ " does not exist. Could not determine karaf port assignment" );
} catch ( Exception e ) {
throw new RuntimeException( "Could not parse file " + instanceFilePath
+ ".", e );
}
}
protected static IKarafInstanceResolver resolver;
protected static IKarafInstanceResolver getResolver() {
if ( resolver == null ) {
String clazz = System.getProperty( KarafBoot.PENTAHO_KARAF_INSTANCE_RESOLVER_CLASS );
if ( clazz != null ) {
try {
resolver = (IKarafInstanceResolver) Class.forName( clazz ).newInstance();
} catch ( Exception e ) {
logger.error( "Error instantiating user defined Karaf Instance Resolver: " + clazz, e );
}
}
if ( resolver == null ) {
// Something went wrong or we're using the default
resolver = new ServerSocketBasedKarafInstanceResolver();
}
}
return resolver;
}
public void assignPortsAndCreateCache() throws KarafInstanceResolverException {
getResolver().resolveInstance( this );
if ( started ) {
throw new IllegalStateException( "Attempt to start a karaf instance that is already started" );
}
started = true;
banner.append( "\n" ).append( StringUtils.repeat( "*", BANNER_WIDTH ) );
bannerLine( "Karaf Instance Number: " + instanceNumber + " at " + cachePath );
SortedSet<String> ids = new TreeSet<>( instancePorts.keySet() );
for ( String id : ids ) {
ServerPort propertyInstance = instancePorts.get( id );
bannerLine( propertyInstance.getFriendlyName() + ":" + propertyInstance.getAssignedPort() );
}
banner.append( "\n" ).append( StringUtils.repeat( "*", BANNER_WIDTH ) );
logger.info( banner.toString() );
// Writing the used ports
String usedPortFilePath = cachePath + "/" + USED_PORT_FILENAME;
try {
PortFileManager.getInstance().writeUsedPortFile( usedPortFilePath );
} catch ( Exception e ) {
// If the ports couldn't be written, log it
logger.warn( "Could not write " + usedPortFilePath + ".", e );
}
}
private void bannerLine( String line ) {
if ( line.length() > BANNER_WIDTH - 8 ) {
bannerLine( line.substring( 0, BANNER_WIDTH - 8 ) );
line = " " + line.substring( BANNER_WIDTH - 8 );
}
banner.append( "\n*** " ).append( line ).append( StringUtils.repeat( " ", BANNER_WIDTH - line.length() - 7 ) )
.append( "***" );
}
public void close() throws IOException {
instanceSocket.close();
cacheLock.acquiredBy().close();
}
public int getInstanceNumber() {
return instanceNumber;
}
public String getCachePath() {
return cachePath;
}
public void registerPort( KarafInstancePort instancePort ) {
if ( started ) {
throw new IllegalStateException( "Ports must be added Before the Karaf instance is started" );
}
if ( instancePorts.containsKey( instancePort.getId() ) ) {
throw new IllegalStateException( "Id " + instancePort.getId() + " already defined." );
}
instancePorts.put( instancePort.getId(), instancePort );
ServerPortRegistry.addPort( instancePort );
}
public KarafInstancePort getPort( String id ) {
return instancePorts.get( id );
}
public Set<String> getPortIds() {
return instancePorts.keySet();
}
public List<KarafInstancePort> getPorts() {
return new ArrayList<>( instancePorts.values() );
}
@VisibleForTesting String getCacheParentFolder() {
return cacheParentFolder;
}
public void setInstanceNumber( Integer instanceNumber ) {
this.instanceNumber = instanceNumber;
}
public void setInstanceSocket( ServerSocket instanceSocket ) {
this.instanceSocket = instanceSocket;
}
public String getClientType() {
return clientType;
}
public void setCacheLock( FileLock cacheLock ) {
this.cacheLock = cacheLock;
}
}