/**
* Copyright 2013-2017 Linagora, Université Joseph Fourier, Floralis
*
* The present code is developed in the scope of the joint LINAGORA -
* Université Joseph Fourier - Floralis research program and is designated
* as a "Result" pursuant to the terms and conditions of the LINAGORA
* - Université Joseph Fourier - Floralis research program. Each copyright
* holder of Results enumerated here above fully & independently holds complete
* ownership of the complete Intellectual Property rights applicable to the whole
* of said Results, and may freely exploit it in any manner which does not infringe
* the moral rights of the other copyright holders.
*
* Licensed 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 net.roboconf.plugin.file.internal;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URI;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.roboconf.core.model.beans.Import;
import net.roboconf.core.model.beans.Instance;
import net.roboconf.core.model.beans.Instance.InstanceStatus;
import net.roboconf.core.model.helpers.InstanceHelpers;
import net.roboconf.core.utils.UriUtils;
import net.roboconf.core.utils.Utils;
import net.roboconf.plugin.api.PluginException;
import net.roboconf.plugin.api.PluginInterface;
/**
* @author Vincent Zurczak - Linagora
*/
public class PluginFile implements PluginInterface {
public static final String PLUGIN_NAME = "file";
static final String FILE_NAME = "instructions.properties";
static final String TMP_FILE = "roboconf_tmp_file";
private final Logger logger = Logger.getLogger( getClass().getName());
private String agentId;
@Override
public String getPluginName() {
return PLUGIN_NAME;
}
@Override
public void setNames( String applicationName, String rootInstanceName ) {
this.agentId = "'" + rootInstanceName + "' agent";
}
@Override
public void initialize( Instance instance ) throws PluginException {
this.logger.fine( this.agentId + " has nothing to initialize (file extension)." );
}
@Override
public void deploy( Instance instance ) throws PluginException {
this.logger.info( this.agentId + " is deploying instance " + instance + "." );
execute( "deploy", instance );
}
@Override
public void start( Instance instance ) throws PluginException {
this.logger.info( this.agentId + " is starting instance " + instance + "." );
execute( "start", instance );
}
@Override
public void update( Instance instance, Import importChanged, InstanceStatus statusChanged ) throws PluginException {
this.logger.info( this.agentId + " is updating instance " + instance + "." );
execute( "update", instance );
}
@Override
public void stop( Instance instance ) throws PluginException {
this.logger.info( this.agentId + " is stopping instance " + instance + "." );
execute( "stop", instance );
}
@Override
public void undeploy( Instance instance ) throws PluginException {
this.logger.info( this.agentId + " is undeploying instance " + instance + "." );
execute( "undeploy", instance );
}
/**
*
* @param actionName
* @param instance
* @throws PluginException
*/
private void execute( String actionName, Instance instance ) throws PluginException {
Properties props = readProperties( instance );
for( Action action : findActions( actionName, props ))
executeAction( action );
}
/**
* Reads the "instructions.properties" file.
* @param instance the instance
* @return a non-null properties object (potentially empty)
* @throws PluginException
*/
Properties readProperties( Instance instance ) throws PluginException {
Properties result = null;
File instanceDirectory = InstanceHelpers.findInstanceDirectoryOnAgent( instance );
File file = new File( instanceDirectory, FILE_NAME );
try {
if( file.exists()) {
result = Utils.readPropertiesFile( file );
} else {
this.logger.warning( file + " does not exist or is invalid. There is no instruction for the plugin." );
result = new Properties();
}
} catch( IOException e ) {
throw new PluginException( e );
}
return result;
}
/**
* Finds the actions to execute for a given step.
* @return a non-null list of actions (sorted in the right execution order)
*/
SortedSet<Action> findActions( String actionName, Properties properties ) {
Pattern pattern = Pattern.compile( actionName + "\\.(\\d)+\\.(.*)", Pattern.CASE_INSENSITIVE );
SortedSet<Action> result = new TreeSet<Action>( new ActionComparator());
for( Map.Entry<Object,Object> entry : properties.entrySet()) {
String key = String.valueOf( entry.getKey()).toLowerCase();
Matcher m = pattern.matcher( key );
if( ! m.matches())
continue;
int position = Integer.parseInt( m.group( 1 ));
ActionType actionType = ActionType.which( m.group( 2 ));
String parameter = String.valueOf( entry.getValue());
result.add( new Action( position, actionType, parameter ));
}
return result;
}
/**
* Executes an action.
* @param action the action to execute
* @throws PluginException
*/
void executeAction( Action action ) throws PluginException {
try {
switch( action.actionType ) {
case DELETE:
this.logger.fine( "Deleting " + action.parameter + "..." );
File f = new File( action.parameter );
Utils.deleteFilesRecursively( f );
break;
case DOWNLOAD:
this.logger.fine( "Downloading " + action.parameter + "..." );
URI uri = UriUtils.urlToUri( action.parameter );
File targetFile = new File( System.getProperty( "java.io.tmpdir" ), TMP_FILE );
InputStream in = null;
try {
in = uri.toURL().openStream();
Utils.copyStream( in, targetFile );
} finally {
Utils.closeQuietly( in );
}
break;
case MOVE:
List<String> parts = Utils.splitNicely( action.parameter, "->" );
if( parts.size() != 2 ) {
this.logger.warning( "Invalid syntax for 'move' action. " + action.parameter );
} else {
File source = new File( parts.get( 0 ));
File target = new File( parts.get( 1 ));
this.logger.fine( "Moving " + source + " to " + target + "..." );
if( ! source.renameTo( target ))
throw new IOException( source + " could not be moved to " + target );
}
break;
case COPY:
parts = Utils.splitNicely( action.parameter, "->" );
if( parts.size() != 2 ) {
this.logger.warning( "Invalid syntax for 'copy' action. " + action.parameter );
} else {
File source = new File( parts.get( 0 ));
File target = new File( parts.get( 1 ));
this.logger.fine( "Copying " + source + " to " + target + "..." );
Utils.copyStream( source, target );
}
break;
default:
this.logger.fine( "Ignoring the action..." );
break;
}
} catch( Exception e ) {
throw new PluginException( e );
}
}
/**
* @author Vincent Zurczak - Linagora
*/
static class Action {
int position;
String parameter;
ActionType actionType;
/**
* Constructor.
* @param position
* @param actionType
* @param parameter
*/
public Action( int position, ActionType actionType, String parameter ) {
this.position = position;
this.actionType = actionType;
this.parameter = parameter;
}
@Override
public boolean equals( Object obj ) {
return obj instanceof Action
&& this.position == ((Action) obj).position
&& this.actionType == ((Action) obj).actionType;
}
@Override
public int hashCode() {
return this.actionType == null ? 23 : this.actionType.toString().hashCode();
}
}
/**
* @author Vincent Zurczak - Linagora
*/
private static class ActionComparator implements Serializable, Comparator<Action> {
private static final long serialVersionUID = 6157709801342723302L;
@Override
public int compare( Action a1, Action a2 ) {
return a1.position - a2.position;
}
}
/**
* @author Vincent Zurczak - Linagora
*/
static enum ActionType {
NOTHING, DOWNLOAD, MOVE, COPY, DELETE;
/**
* A case-insensitive version of {@link #valueOf(String)}.
* @param s any string
* @return an action type, {@value #NOTHING} by default
*/
public static ActionType which( String s ) {
ActionType result = NOTHING;
for( ActionType at : values()) {
if( at.toString().equalsIgnoreCase( s )) {
result = at;
break;
}
}
return result;
}
}
}