/**
* Copyright 2015-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.dm.rest.services.internal.resources.impl;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import com.sun.jersey.core.header.FormDataContentDisposition;
import net.roboconf.core.Constants;
import net.roboconf.core.model.ModelError;
import net.roboconf.core.model.TargetValidator;
import net.roboconf.core.model.beans.AbstractApplication;
import net.roboconf.core.model.helpers.RoboconfErrorHelpers;
import net.roboconf.core.model.runtime.TargetUsageItem;
import net.roboconf.core.model.runtime.TargetWrapperDescriptor;
import net.roboconf.core.utils.Utils;
import net.roboconf.dm.management.Manager;
import net.roboconf.dm.management.exceptions.UnauthorizedActionException;
import net.roboconf.dm.rest.commons.json.StringWrapper;
import net.roboconf.dm.rest.services.internal.resources.ITargetResource;
import net.roboconf.dm.rest.services.internal.utils.RestServicesUtils;
/**
* @author Vincent Zurczak - Linagora
*/
@Path( ITargetResource.PATH )
public class TargetResource implements ITargetResource {
private final Logger logger = Logger.getLogger( getClass().getName());
private final Manager manager;
private final Map<Class<?>,Status> exceptionclassToErrorCode;
/**
* Constructor.
* @param manager the manager
*/
public TargetResource( Manager manager ) {
this.manager = manager;
// This class allows to have several exception blocks.
// It is more efficient for code coverage and guarantees a coherent
// handling of exceptions.
this.exceptionclassToErrorCode = new HashMap<>( 2 );
this.exceptionclassToErrorCode.put( IOException.class, Status.INTERNAL_SERVER_ERROR );
this.exceptionclassToErrorCode.put( UnauthorizedActionException.class, Status.FORBIDDEN );
}
@Override
public List<TargetWrapperDescriptor> listTargets( String name, String qualifier ) {
StringBuilder sb = new StringBuilder( "Request: list targets." );
if( name != null ) {
sb.append( " Filter names: " + name );
if( qualifier != null )
sb.append( ", " + qualifier );
}
this.logger.fine( sb.toString());
AbstractApplication app = findAbstractApplication( name, qualifier );
List<TargetWrapperDescriptor> result;
if( app == null )
result = this.manager.targetsMngr().listAllTargets();
else
result = this.manager.targetsMngr().listPossibleTargets( app );
return result;
}
@Override
public Response createOrUpdateTarget( String rawProperties, String targetId ) {
if( targetId == null )
this.logger.fine( "Request: create a new target." );
else
this.logger.fine( "Request: update target " + targetId + "." );
Response response;
String id = targetId;
try {
if( targetId == null )
id = this.manager.targetsMngr().createTarget( rawProperties );
else
this.manager.targetsMngr().updateTarget( targetId, rawProperties );
response = Response.ok().entity( id ).build();
} catch( IOException | UnauthorizedActionException e ) {
Status status = this.exceptionclassToErrorCode.get( e.getClass());
response = RestServicesUtils.handleException( this.logger, status, null, e ).build();
}
return response;
}
@Override
public Response loadTargetArchive( InputStream uploadedInputStream, FormDataContentDisposition fileDetail ) {
this.logger.fine( "Request: load targets from an uploaded ZIP file (" + fileDetail.getFileName() + ")." );
File tempZipFile = new File( System.getProperty( "java.io.tmpdir" ), fileDetail.getFileName());
final Set<String> newTargetIds = new HashSet<> ();
File dir = null;
Response response;
try {
// Copy the uploaded ZIP file on the disk
Utils.copyStream( uploadedInputStream, tempZipFile );
// Extract the ZIP content
String projectName = fileDetail.getFileName().replace( ".zip", "" );
dir = new File( System.getProperty( "java.io.tmpdir" ), "roboconf/" + projectName );
Utils.extractZipArchive( tempZipFile, dir );
// Validate the content
List<ModelError> errors = TargetValidator.parseDirectory( dir );
if( RoboconfErrorHelpers.containsCriticalErrors( errors )) {
response = RestServicesUtils.handleException( this.logger, Status.FORBIDDEN, "Target properties contain errors." ).build();
}
// Register the properties
else {
response = Response.ok().build();
for( File f : Utils.listAllFiles( dir, Constants.FILE_EXT_PROPERTIES )) {
String id = this.manager.targetsMngr().createTarget( f, null );
newTargetIds.add( id );
}
}
} catch( Exception e ) {
response = RestServicesUtils.handleException( this.logger, Status.NOT_ACCEPTABLE, "Target properties could not be loaded.", e ).build();
// Unregister the targets
this.logger.fine( "An error occurred while deploying targets. Unregistering those that were in the same archive." );
for( String id : newTargetIds ) {
try {
this.manager.targetsMngr().deleteTarget( id );
} catch( Exception e1 ) {
Utils.logException( this.logger, e1 );
this.logger.severe( "Target " + id + " could not be deleted." );
}
}
} finally {
Utils.closeQuietly( uploadedInputStream );
// We do not need the extracted application anymore.
// In case of success, it was copied in the DM's configuration.
Utils.deleteFilesRecursivelyAndQuietly( dir );
Utils.deleteFilesRecursivelyAndQuietly( tempZipFile );
}
return response;
}
@Override
public Response deleteTarget( String targetId ) {
this.logger.fine( "Request: delete target " + targetId + "." );
Response response = Response.ok().build();
try {
this.manager.targetsMngr().deleteTarget( targetId );
} catch( IOException | UnauthorizedActionException e ) {
Status status = this.exceptionclassToErrorCode.get( e.getClass());
response = RestServicesUtils.handleException( this.logger, status, null, e ).build();
}
return response;
}
@Override
public Response getTargetProperties( String targetId ) {
this.logger.fine( "Request: get properties for target " + targetId + "." );
String content = this.manager.targetsMngr().findRawTargetProperties( targetId );
Response response;
if( content == null )
response = Response.status( Status.NOT_FOUND ).build();
else
response = Response.ok().entity( new StringWrapper( content )).build();
return response;
}
@Override
public Response findTargetById( String targetId ) {
this.logger.fine( "Request: get details about target " + targetId + "." );
TargetWrapperDescriptor twb = this.manager.targetsMngr().findTargetById( targetId );
Response response;
if( twb == null )
response = Response.status( Status.NOT_FOUND ).build();
else
response = Response.ok().entity( twb ).build();
return response;
}
@Override
public Response associateTarget(
String name,
String qualifier,
String instancePathOrComponentName,
String targetId,
boolean bind ) {
if( bind )
this.logger.fine( "Request: associate " + instancePathOrComponentName + " with target " + targetId + "." );
else
this.logger.fine( "Request: dissociate " + instancePathOrComponentName + " from its target." );
Response response = Response.ok().build();
AbstractApplication app = findAbstractApplication( name, qualifier );
try {
if( app == null )
response = Response.status( Status.BAD_REQUEST ).build();
else if( bind )
this.manager.targetsMngr().associateTargetWith( targetId, app, instancePathOrComponentName );
else
this.manager.targetsMngr().dissociateTargetFrom( app, instancePathOrComponentName );
} catch( IOException | UnauthorizedActionException e ) {
Status status = this.exceptionclassToErrorCode.get( e.getClass());
response = RestServicesUtils.handleException( this.logger, status, null, e ).build();
}
return response;
}
@Override
public Response updateHint( String name, String qualifier, String targetId, boolean bind ) {
String id = name;
if( qualifier != null )
id += " - " + qualifier;
if( bind )
this.logger.fine( "Request: add a hint between " + id + " and target " + targetId + "." );
else
this.logger.fine( "Request: remove a hint between " + id + " and target " + targetId + "." );
Response response = Response.ok().build();
AbstractApplication app = findAbstractApplication( name, qualifier );
try {
if( app == null )
response = Response.status( Status.BAD_REQUEST ).build();
else if( bind )
this.manager.targetsMngr().addHint( targetId, app );
else
this.manager.targetsMngr().removeHint( targetId, app );
} catch( IOException e ) {
response = RestServicesUtils.handleException( this.logger, Status.INTERNAL_SERVER_ERROR, null, e ).build();
}
return response;
}
@Override
public List<TargetUsageItem> findUsageStatistics( String targetId ) {
this.logger.fine( "Request: list usage statistics for target " + targetId + "." );
return this.manager.targetsMngr().findUsageStatistics( targetId );
}
private AbstractApplication findAbstractApplication( String name, String qualifier ) {
AbstractApplication app = null;
if( name != null ) {
if( qualifier != null )
app = this.manager.applicationTemplateMngr().findTemplate( name, qualifier );
else
app = this.manager.applicationMngr().findApplicationByName( name );
}
return app;
}
}