/*
* 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.apache.usergrid.chop.webapp.coordinator.rest;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import javax.annotation.Nullable;
import javax.mail.internet.MimeMultipart;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.usergrid.chop.stack.SetupStackState;
import org.apache.usergrid.chop.webapp.coordinator.StackCoordinator;
import org.safehaus.jettyjam.utils.TestMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.usergrid.chop.api.Commit;
import org.apache.usergrid.chop.api.Constants;
import org.apache.usergrid.chop.api.Module;
import org.apache.usergrid.chop.api.Project;
import org.apache.usergrid.chop.api.RestParams;
import org.apache.usergrid.chop.webapp.ChopUiFig;
import org.apache.usergrid.chop.webapp.coordinator.CoordinatorUtils;
import org.apache.usergrid.chop.webapp.dao.CommitDao;
import org.apache.usergrid.chop.webapp.dao.ModuleDao;
import org.apache.usergrid.chop.webapp.dao.model.BasicCommit;
import org.apache.usergrid.chop.webapp.dao.model.BasicModule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.sun.jersey.multipart.FormDataParam;
/**
* REST operation to upload (a.k.a. deploy) a project war file.
*/
@Singleton
@Produces( MediaType.TEXT_PLAIN )
@Path( UploadResource.ENDPOINT )
public class UploadResource extends TestableResource implements RestParams, Constants {
public final static String ENDPOINT = "/upload";
private final static Logger LOG = LoggerFactory.getLogger( UploadResource.class );
@Inject
private ChopUiFig chopUiFig;
@Inject
private ModuleDao moduleDao;
@Inject
private CommitDao commitDao;
@Inject
private StackCoordinator stackCoordinator;
public UploadResource() {
super( ENDPOINT );
}
/**
* Uploads a file to the servlet context temp directory. More for testing proper uploads.
*/
@POST
@Consumes( MediaType.MULTIPART_FORM_DATA )
@Produces( MediaType.APPLICATION_JSON )
public Response upload( MimeMultipart multipart )
{
try {
String filename = multipart.getBodyPart( 0 ).getContent().toString();
LOG.warn( "FILENAME: " + filename );
InputStream in = multipart.getBodyPart( 1 ).getInputStream();
File tempDir = new File( chopUiFig.getContextTempDir() );
String fileLocation = new File( tempDir, filename ).getAbsolutePath();
CoordinatorUtils.writeToFile( in, fileLocation );
}
catch ( Exception ex ) {
LOG.error( "upload", ex );
return Response.status( Response.Status.INTERNAL_SERVER_ERROR ).entity( ex.getMessage() ).build();
}
return Response.status( Response.Status.CREATED ).entity( "ok" ).build();
}
@POST
@Consumes( MediaType.APPLICATION_JSON )
@Produces( MediaType.APPLICATION_JSON )
@Path( "/status" )
public Response runnerStatus(
@QueryParam( RestParams.COMMIT_ID ) String commitId,
@QueryParam( RestParams.MODULE_ARTIFACTID ) String artifactId,
@QueryParam( RestParams.MODULE_GROUPID ) String groupId,
@QueryParam( RestParams.MODULE_VERSION ) String version,
@QueryParam( RestParams.USERNAME ) String username,
@QueryParam( VCS_REPO_URL ) String vcsRepoUrl,
@QueryParam( TEST_PACKAGE ) String testPackage,
@QueryParam( MD5 ) String md5,
@QueryParam( RestParams.RUNNER_COUNT ) int runnerCount,
@Nullable @QueryParam( TestMode.TEST_MODE_PROPERTY ) String testMode
) throws IOException {
if( inTestMode( testMode ) ) {
LOG.info( "Calling /upload/status in test mode ..." );
}
else {
LOG.info( "Calling /upload/status" );
}
File runnerJar = CoordinatorUtils.getRunnerJar( chopUiFig.getContextPath(), username, groupId, artifactId,
version, commitId );
SetupStackState status = stackCoordinator.stackStatus( commitId, artifactId, groupId, version, username );
if( runnerJar.exists() ) {
String coordinatorRunnerJarMd5 = getCoordinatorJarMd5( runnerJar.getAbsolutePath() );
if ( isMD5SumsEqual( coordinatorRunnerJarMd5, md5 ) ) {
return Response.status( Response.Status.OK )
.entity( SetupStackState.NotSetUp.getStackStateMessage() )
.build();
}
}
return Response.status( Response.Status.OK )
.entity( SetupStackState.JarNotFound.getStackStateMessage() )
.build();
}
/**
* Uploads an executable runner jar into a special path in the temp directory for the application.
*/
@POST
@Path( "/runner" )
@Consumes( MediaType.MULTIPART_FORM_DATA )
@Produces( MediaType.TEXT_PLAIN )
public Response uploadRunner(
@FormDataParam( COMMIT_ID ) String commitId,
@FormDataParam( MODULE_GROUPID ) String groupId,
@FormDataParam( MODULE_ARTIFACTID ) String artifactId,
@FormDataParam( MODULE_VERSION ) String version,
@FormDataParam( USERNAME ) String username,
@FormDataParam( VCS_REPO_URL ) String vcsRepoUrl,
@FormDataParam( TEST_PACKAGE ) String testPackage,
@FormDataParam( MD5 ) String md5,
@FormDataParam( RestParams.RUNNER_COUNT ) int runnerCount,
@FormDataParam( CONTENT ) InputStream runnerJarStream,
@Nullable @QueryParam( TestMode.TEST_MODE_PROPERTY ) String testMode
) throws Exception {
if( inTestMode( testMode ) ) {
LOG.info( "Calling /upload/runner in test mode ..." );
}
else {
LOG.info( "/upload/runner called ..." );
}
LOG.debug( "extracted {} = {}", RestParams.COMMIT_ID, commitId );
LOG.debug( "extracted {} = {}", RestParams.MODULE_GROUPID, groupId );
LOG.debug( "extracted {} = {}", RestParams.MODULE_ARTIFACTID, artifactId );
LOG.debug( "extracted {} = {}", RestParams.MODULE_VERSION, version );
LOG.debug( "extracted {} = {}", RestParams.USERNAME, username );
LOG.debug( "extracted {} = {}", RestParams.VCS_REPO_URL, vcsRepoUrl );
LOG.debug( "extracted {} = {}", RestParams.TEST_PACKAGE, testPackage );
LOG.debug( "extracted {} = {}", RestParams.RUNNER_COUNT, runnerCount );
LOG.debug( "extracted {} = {}", RestParams.MD5, md5 );
if( inTestMode( testMode ) ) {
return Response.status( Response.Status.CREATED )
.entity( SUCCESSFUL_TEST_MESSAGE )
.type( MediaType.TEXT_PLAIN )
.build();
}
File runnerJar = CoordinatorUtils.getRunnerJar( chopUiFig.getContextPath(), username, groupId, artifactId,
version, commitId );
if ( ! runnerJar.getParentFile().exists() ) {
if ( runnerJar.getParentFile().mkdirs() ) {
LOG.info( "Created parent directory {} for uploaded runner file", runnerJar.getAbsolutePath() );
}
else {
String errorMessage = "Failed to create parent directory " + runnerJar.getAbsolutePath()
+ " for uploaded runner file.";
LOG.error( errorMessage );
return Response.status( Response.Status.INTERNAL_SERVER_ERROR ).entity( errorMessage ).build();
}
}
if( runnerJar.exists() ) {
if( runnerJar.delete() ) {
LOG.info( "Deleted old runner.jar" );
}
else {
LOG.info( "Could not delete old runner.jar" );
}
}
// Download and write the file to the proper position on disk & reference
CoordinatorUtils.writeToFile( runnerJarStream, runnerJar.getAbsolutePath() );
// - this is bad news because we will get commits of other users :(
// - we also need to qualify the commit with username, groupId,
// and the version of module as well
Commit commit = null;
Module module = null;
List<Commit> commits = commitDao.getByModule( artifactId );
for ( Commit returnedCommit : commits ) {
Module commitModule = moduleDao.get( returnedCommit.getModuleId() );
if ( commitModule.getArtifactId().equals( artifactId ) &&
commitModule.getGroupId().equals( groupId ) &&
commitModule.getVersion().equals( version ) )
{
commit = returnedCommit;
module = commitModule;
}
}
if ( module == null ) {
module = new BasicModule( groupId, artifactId, version, vcsRepoUrl, testPackage );
moduleDao.save( module );
}
if ( commit == null ) {
commit = new BasicCommit( commitId, module.getId(), md5, new Date(), runnerJar.getAbsolutePath() );
commitDao.save( commit );
}
stackCoordinator.registerStack( commitId, artifactId, groupId, version, username, runnerCount );
return Response.status( Response.Status.CREATED ).entity( runnerJar.getAbsolutePath() ).build();
}
private boolean isMD5SumsEqual( final String coordinatorRunnerJarMd5Sum, final String localRunnerJarMd5Sum ) {
return coordinatorRunnerJarMd5Sum.equals( localRunnerJarMd5Sum );
}
public String getCoordinatorJarMd5( String coordinatorRunnerJarPath ) {
InputStream stream;
URL inputURL;
Properties props = new Properties();
String runnerJarProjectPropertiesFile = "jar:file:" + coordinatorRunnerJarPath + "!/" + PROJECT_FILE;
if ( runnerJarProjectPropertiesFile.startsWith( "jar:" ) ) {
try {
inputURL = new URL( runnerJarProjectPropertiesFile );
JarURLConnection conn = ( JarURLConnection ) inputURL.openConnection();
stream = conn.getInputStream();
InputStreamReader reader = new InputStreamReader( stream );
props.load( reader );
stream.close();
} catch ( MalformedURLException e ) {
LOG.error( "Malformed URL provided:", e );
} catch ( IOException e ) {
LOG.error( "Error while reading the file:", e );
}
}
String coordinatorJarMd5 = props.getProperty( Project.MD5_KEY );
return coordinatorJarMd5;
}
}