/*****************************************************************************
* Copyright (c) 2008, g-Eclipse Consortium
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Initial development of the original code was made for the
* g-Eclipse project founded by European Union
* project number: FP6-IST-034327 http://www.geclipse.eu/
*
* Contributors:
* Szymon Mueller - PSNC - Initial API and implementation
*****************************************************************************/
package eu.geclipse.servicejob.model.submittable.job;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.filesystem.IFileSystem;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.w3c.dom.DOMException;
import org.xml.sax.SAXException;
import eu.geclipse.core.ICoreProblems;
import eu.geclipse.core.model.IGridJobDescription;
import eu.geclipse.core.model.IGridJobID;
import eu.geclipse.core.model.IGridJobService;
import eu.geclipse.core.model.IGridJobStatus;
import eu.geclipse.core.model.IServiceJob;
import eu.geclipse.core.model.IServiceJobResult;
import eu.geclipse.core.model.IVirtualOrganization;
import eu.geclipse.core.reporting.ProblemException;
import eu.geclipse.servicejob.Activator;
import eu.geclipse.servicejob.model.AbstractServiceJob;
import eu.geclipse.servicejob.parsers.GTDLJobParser;
import eu.geclipse.servicejob.parsers.GTDLJobWriter;
/**
* Abstract class implementing basic {@link IServiceJob} methods for job based
* service jobs. The implementation follows this specified workflow:
* <ul>
* <li>1. For each target resource single JSDL is created.
* <li>2. Each JSDL is submitted using submission service.
* <li>3. Jobs are monitored for their termination.
* <li>4. When all of the submitted jobs are finished some additional actions
* are performed by main service job.
* </ul>
* If the service job varies from the above workflow, some of the methods should
* be overwritten.
*/
public abstract class AbstractSubmittableServiceJob extends AbstractServiceJob {
/**
* File name for storing info of the service job.
*/
public static final String SERVICE_JOB_INFO_FILENAME
= ".servicejobinfo"; //$NON-NLS-1$
/**
* XML charset
*/
public static final String XML_CHARSET = "ISO-8859-1"; //$NON-NLS-1$
/**
* Extension of the file storing info of the service job results for the
* specified nodes
*/
public static final String SERVICE_JOB_STATUS_FILENAME
= ".servicejobstatus"; //$NON-NLS-1$
protected Map<String, String> jobIDResourceNameMap = new HashMap<String, String>();
protected ServiceJobUpdater updater;
public boolean isLocal() {
return true;
}
public void init() {
this.name = getResource().getName().substring( 0,
getResource().getName()
.lastIndexOf( "." ) ); //$NON-NLS-1$
IFile file = ( IFile )getResource();
try {
List<SubmittableServiceJobResult> tempRes = GTDLJobParser.getServiceJobResults( file.getRawLocation()
.toFile() );
this.results = new ArrayList<IServiceJobResult>();
for( SubmittableServiceJobResult result : tempRes ) {
this.results.add( result );
result.setJobID( createJobID( result ) );
}
List<IGridJobID> jobIDsToRun = new ArrayList<IGridJobID>();
for( SubmittableServiceJobResult serviceJobResult : tempRes ) {
if( serviceJobResult.getResultEnum()
.equals( Messages.getString( "AbstractSubmittableJob.running_status" ) ) //$NON-NLS-1$
|| serviceJobResult.getResultEnum()
.equals( Messages.getString( "AbstractSubmittableJob.pending_status" ) ) ) { //$NON-NLS-1$
jobIDsToRun.add( serviceJobResult.getJobID() );
}
}
if( jobIDsToRun.size() > 0 ) {
ServiceJobUpdater serviceJobUpdater = getUpdater();
serviceJobUpdater.addSubJobs( jobIDsToRun );
serviceJobUpdater.schedule( 30000 );
}
} catch( ParserConfigurationException e ) {
// TODO szymon proper error handling
Activator.logException( e );
} catch( SAXException e ) {
// TODO szymon proper error handling
Activator.logException( e );
} catch( IOException e ) {
// TODO szymon proper error handling
Activator.logException( e );
} catch( DOMException e ) {
// TODO szymon proper error handling
Activator.logException( e );
} catch( ParseException e ) {
// TODO szymon proper error handling
Activator.logException( e );
}
initData();
}
/**
* Implementations of this method should create and return proper,
* middleware-specific job id. This usually will require some additional
* informations (e.g. job ID). Clients should use
* <code>ServiceJobResult.getResultRawData()</code> and parse needed
* information.
*
* @param serviceJobResult Job result for which job ID should be created.
* @return IGridJobID representing the service job. If no middleware specific
* id can be returned this should return generic GridJobID class.
*/
public abstract IGridJobID createJobID(
final SubmittableServiceJobResult serviceJobResult );
/**
* Perform additional, service job specific tasks at the start of the service
* job. If needed, <code>inputString</code> representing input part of XML may
* be parsed for additional informations about the service job.
*/
public abstract void initData();
/**
* Getter of this service job updater.
*
* @return Service job updater for this service job.
*/
public ServiceJobUpdater getUpdater() {
if( this.updater == null ) {
this.updater = new ServiceJobUpdater( getName(), this );
}
return this.updater;
}
public void run() {
Job runJob = new Job( Messages.getString( "AbstractSubmittableJob.submitting" ) //$NON-NLS-1$
+ " " //$NON-NLS-1$
+ getName()
+ " " //$NON-NLS-1$
+ Messages.getString( "AbstractSubmittableJob.job" ) ) { //$NON-NLS-1$
@Override
protected IStatus run( final IProgressMonitor uMonitor ) {
IStatus status = Status.OK_STATUS;
IProgressMonitor monitor = uMonitor != null
? uMonitor
: new NullProgressMonitor();
List<IGridJobID> jobIDsToAdd = new ArrayList<IGridJobID>();
for( String resourceName : getServiceJobResourcesNames() ) {
IGridJobDescription description = getJSDLForSubmission( resourceName );
IGridJobService jobService = getSubmissionService();
if( description != null && jobService != null ) {
try {
IGridJobID jobID = jobService.submitJob( description, monitor );
AbstractSubmittableServiceJob.this.jobIDResourceNameMap.put( jobID.getJobID(),
resourceName );
jobIDsToAdd.add( jobID );
createNewResult( jobID, resourceName, Calendar.getInstance()
.getTime() );
} catch( ProblemException exc ) {
status = new Status( IStatus.ERROR,
Activator.PLUGIN_ID,
Messages.getString( "AbstractSubmittableJob.Error_submitting_job" ), //$NON-NLS-1$
exc );
}
}
}
if( jobIDsToAdd.size() > 0 ) {
ServiceJobUpdater serviceJobUpdater = getUpdater();
serviceJobUpdater.addSubJobs( jobIDsToAdd );
serviceJobUpdater.schedule( 30000 );
}
return status;
}
};
runJob.schedule();
}
// /**
// * Method returns job submission service for the resource with the specified
// * name. If no submission service is returned, job's can't be submitted.
// *
// * @param resourceName
// * @return IGridJobService to which all JSDL testing specified resource name
// * are submitted
// */
// public abstract IGridJobService getSubmissionService( final String
// resourceName );
/**
* Gets the JSDL which will be submitted to resource with name.
*
* @param resourceName
* @return JSDLJobDescription if the JSDL is ready for submission or null if
* it is not
*/
public abstract IGridJobDescription getJSDLForSubmission( final String resourceName );
/**
* Sets result of the running grid job with jobID, storing the result in the
* status file of this job. If result for the job with jobID exists the result
* is overwritten by the parser (the newly fetched status of the job is
* written). If there is no job result for the given jobID yet stored, then
* new XML node is added to the status XML for that jobID.
* <p>
* Developers can overwrite this method if the storage of the status results
* is resolved differently.
* </p>
*
* @param jobID id of the running job
* @param lastRefreshDate date of the last refresh of job status
* @param status fetched job status
* @param besStatus BES status of the job
*/
public void setJobResult( final IGridJobID jobID,
final Date lastRefreshDate,
final String status,
final String besStatus )
{
SubmittableServiceJobResult serviceJobResult = null;
for( IServiceJobResult tempResult1 : this.results ) {
SubmittableServiceJobResult tempResult = ( SubmittableServiceJobResult )tempResult1;
if( tempResult.getJobIDString().equals( jobID.getJobID() ) ) {
serviceJobResult = tempResult;
}
}
if( serviceJobResult != null ) {
serviceJobResult.updateStatus( lastRefreshDate, status, besStatus );
List<SubmittableServiceJobResult> ress = new ArrayList<SubmittableServiceJobResult>();
ress.add( serviceJobResult );
try {
GTDLJobWriter.addServiceJobResults( getResource().getRawLocation().toFile(),
ress );
this.lastResult = serviceJobResult;
if( !getResource().isSynchronized( IResource.DEPTH_ZERO ) ) {
try {
getResource().refreshLocal( IResource.DEPTH_ZERO, null );
} catch( CoreException e ) {
// TODO
Activator.logException( e );
}
}
} catch( ParserConfigurationException e ) {
Activator.logException( e );
} catch( SAXException e ) {
Activator.logException( e );
} catch( IOException e ) {
Activator.logException( e );
} catch( TransformerFactoryConfigurationError e ) {
Activator.logException( e );
} catch( TransformerException e ) {
Activator.logException( e );
}
}
}
/**
* Create new result for this service job.
*
* @param jobID ID of the job for which service job's result should be
* created.
* @param resourceName Name of the resource on which service job should run.
* @param submissionDate Date of the job submission.
*/
public void createNewResult( final IGridJobID jobID,
final String resourceName,
final Date submissionDate )
{
for( String singleServiceJobName : getSingleServiceJobNames() ) {
SubmittableServiceJobResult serviceJobResult
= new SubmittableServiceJobResult( submissionDate,
resourceName,
singleServiceJobName,
getRawDataInput( jobID,
resourceName ),
Messages.getString( "AbstractSubmittableJob.pending_status" ), //$NON-NLS-1$
getResultType( singleServiceJobName ),
Messages.getString( "AbstractSubmittableJob.pending_status" ) ); //$NON-NLS-1$
serviceJobResult.setJobID( jobID );
this.results.add( serviceJobResult );
List<SubmittableServiceJobResult> ress = new ArrayList<SubmittableServiceJobResult>();
ress.add( serviceJobResult );
try {
GTDLJobWriter.addServiceJobResults( getResource().getRawLocation().toFile(),
ress );
if( !getResource().isSynchronized( IResource.DEPTH_ZERO ) ) {
try {
getResource().refreshLocal( IResource.DEPTH_ZERO, null );
} catch( CoreException e ) {
// TODO
Activator.logException( e );
}
}
} catch( ParserConfigurationException e ) {
// TODO szymon proper error handling
Activator.logException( e );
} catch( SAXException e ) {
// TODO szymon proper error handling
Activator.logException( e );
} catch( IOException e ) {
// TODO szymon proper error handling
Activator.logException( e );
} catch( TransformerFactoryConfigurationError e ) {
// TODO szymon proper error handling
Activator.logException( e );
} catch( TransformerException e ) {
// TODO szymon proper error handling
Activator.logException( e );
}
}
}
/**
* This method should return type of the result data returned by single
* service job.
*
* @param singleServiceJobName Name of the single service job (sub-job).
* @return String representing type of the result data (extension).
*/
public abstract String getResultType( final String singleServiceJobName );
/**
* This method should return starting string XML structure of raw data node of
* the given <code>jobID</code> associated with the <code>resourceName</code>.
*
* @param jobID ID of the job for which the node should be created
* @param resourceName Name of the resource.
* @return String representing starting XML of raw data for the service job
* result.
*/
public abstract String getRawDataInput( final IGridJobID jobID,
final String resourceName );
/**
* This method is invoked by service job updater when one of the running jobs is
* finished. Implementation should compute result of the service job or sub-job(s)
* accordingly to the outcome of job with <code>jobID</code>.
*
* @param jobID ID of job which finished running
* @param jobStatus status of the finished job
*/
public abstract void computeJobResult( final IGridJobID jobID,
final IGridJobStatus jobStatus );
/**
* This method is invoked by service job updater after last job of this
* service job is finished and its result was computed by
* {@link #computeJobResult(IGridJobID, IGridJobStatus)}.
*/
public abstract void computeServiceJobResult();
protected InputStream getContentsStream( final String path,
final String bundle )
{
InputStream resultStream = null;
Path resultPath = new Path( path );
URL fileURL = FileLocator.find( Platform.getBundle( bundle ),
resultPath,
null );
try {
fileURL = FileLocator.toFileURL( fileURL );
} catch( IOException ioException ) {
// TODO
Activator.logException( ioException );
} catch( NullPointerException nullExc ) {
// TODO
Activator.logException( nullExc );
}
String temp = fileURL.toString();
temp = temp.substring( temp.indexOf( fileURL.getProtocol() )
+ fileURL.getProtocol().length()
+ 1, temp.length() );
resultPath = new Path( temp );
File file = resultPath.toFile();
try {
resultStream = new FileInputStream( file );
} catch( FileNotFoundException e ) {
// TODO
Activator.logException( e );
}
return resultStream;
}
/**
* Getter of the project's VO for this service job.
*
* @return This serviceJob's VO or null if cannot fetch VO.
*/
public IVirtualOrganization getSelectedJobsVO() {
return getProject() != null
? getProject().getVO()
: null;
}
/**
* Workaround for following problem in g-eclipse: store.openInputStream()
* always return null if IFileStore was obtained directly using URI Instead of
* directly obtaining IFileStore, we should list parent directory and find
* there our file
*
* @param fileSystem
* @param fileUri
* @return
* @throws GridException
*/
protected IFileStore getFileViaDirectory( final IFileSystem fileSystem,
final URI fileUri )
throws ProblemException
{
try {
IPath filePath = new Path( fileUri.getPath() );
IPath folderPath = filePath.removeLastSegments( 1 )
.addTrailingSeparator();
URI dirUri = new URI( fileUri.getScheme(),
fileUri.getUserInfo(),
fileUri.getHost(),
fileUri.getPort(),
folderPath.toString(),
fileUri.getQuery(),
fileUri.getFragment() );
IFileStore dirStore = fileSystem.getStore( dirUri );
dirStore.childNames( 0, null );
return dirStore.getChild( filePath.lastSegment() );
} catch( URISyntaxException exception ) {
throw new ProblemException( ICoreProblems.NET_MALFORMED_URL,
exception,
Activator.PLUGIN_ID );
} catch( CoreException exception ) {
throw new ProblemException( ICoreProblems.IO_UNSPECIFIED_PROBLEM,
exception,
Activator.PLUGIN_ID );
}
}
@Override
public int getColumnWidth( final String singleServiceJobName ) {
return 200;
}
@Override
public boolean needsSubmissionWizard() {
return true;
}
}