package de.zib.gndms.gndmc.gorfx; /* * Copyright 2008-2011 Zuse Institute Berlin (ZIB) * * 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. */ import de.zib.gndms.common.model.gorfx.types.*; import de.zib.gndms.common.rest.Facets; import de.zib.gndms.common.rest.GNDMSResponseHeader; import de.zib.gndms.common.rest.Specifier; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.client.HttpClientErrorException; import java.util.List; import java.util.UUID; /** * @author try ma ik jo rr a zib * @date 14.03.11 11:38 * @brief Performs all requests necessary for taskflow execution. * * To put the caller in control over the taskflow this class provides * handler methods for the result of imported calls. * * \note The handler methods are only called if the preceding server response * was positive. */ public abstract class AbstractTaskFlowExecClient implements TaskStatusHandler { private GORFXClient gorfxClient; ///< A ready to uses instance of the gorfx client. private TaskFlowClient tfClient; ///< A ready to uses instance of the taskflow client. private TaskClient taskClient; ///< A ready to uses instance of the task client. private long pollingDelay = 1000; ///< delay in ms to poll the task status, once the task is running. /** * @brief Executes a complete task flow. * * @param order The order of the taskflow. * @param dn The DN of the user calling the task flow. * \note here the workflow id is generated on the fly. */ public void execTF( Order order, String dn ) { execTF( order, dn, true, null, UUID.randomUUID().toString() ); } /** * * @brief Executes a complete task flow. * * This method is imported when you want to understand the * Taskflow protocol. * * @param order The order of the taskflow. * @param dn The DN of the user calling the task flow. * * @param withQuote Activates co-scheduling * @param desiredQuote A quote holding desired time values for * the tasflow execution. * * \note for now the workflow id is generated on the fly. */ public void execTF( Order order, String dn, boolean withQuote, final Quote desiredQuote ) { execTF( order, dn, withQuote, desiredQuote, UUID.randomUUID().toString() ); } /** * * @brief Executes a complete task flow. * * This method is imported when you want to understand the * Taskflow protocol. * * @param order The order of the taskflow. * @param dn The DN of the user calling the task flow. * * @param withQuote Activates co-scheduling * @param desiredQuote A quote holding desired time values for * the tasflow execution. * @param wid the workflow id */ public void execTF( Order order, String dn, boolean withQuote, final Quote desiredQuote, final String wid ) { GNDMSResponseHeader context = setupContext( new GNDMSResponseHeader() ); if( null == gorfxClient ) { throw new IllegalStateException( "You need to set gorfxClient before executing a TaskFlow!" ); } /** * \code this is important */ // sends the order and creates the task flow ResponseEntity<Specifier<Facets>> res = gorfxClient.createTaskFlow( order.getTaskFlowType(), order, dn, wid, context ); if ( !HttpStatus.CREATED.equals( res.getStatusCode() ) ) { throw new RuntimeException( "createTaskFlow failed " + res.getStatusCode().name() + " (" + res.getStatusCode() + ")" + " on URL " + gorfxClient.getServiceURL() ); } // the taskflow id is stored under "id" in the urlmap String tid = res.getBody().getUriMap().get( "id" ); Integer q = null; if( withQuote ) { if( null == tfClient ) { throw new IllegalStateException( "No TaskFlowClient set." ); } if( desiredQuote != null ) { tfClient.setQuote( order.getTaskFlowType(), tid, desiredQuote, dn, wid ); } // queries the quotes for the task flow ResponseEntity<List<Specifier<Quote>>> res2 = tfClient.getQuotes( order.getTaskFlowType(), tid, dn, wid ); if ( !HttpStatus.OK.equals( res2.getStatusCode() ) ) throw new RuntimeException( "getQuotes failed " + res2.getStatusCode().name() ); // lets the implementors of this class choose a quote q = selectQuote( res2.getBody() ); } // // 'til here it is valid to change the order and request new quotes // // accepts quote q and triggers task creation ResponseEntity<Specifier<Facets>> res3 = tfClient.createTask( order.getTaskFlowType(), tid, q, dn, wid ); if(! HttpStatus.CREATED.equals( res3.getStatusCode() ) ) throw new RuntimeException( "createTask failed " + res3.getStatusCode().name() ); final Specifier<Facets> taskSpecifier = res3.getBody(); // let the implementor do smart things with the task specifier handleTaskSpecifier( taskSpecifier ); // the task id is stored under "taskId" in the specifiers urlmap waitForFinishOrFail( taskSpecifier, this, taskClient, pollingDelay, dn, wid ); /** * \endcode */ } /** * Polls a running task, until its either finished or failed. * * @param taskSpecifier The specifier of the task. * @param statusHandler The handler for the task status, can update some sort of UI. * @param taskClient The task client, which should be used for polling. * @param pollingDelay The pollingDelay, its the delay between polling. * @param dn The user DN. * @param wid The workflow id. * * @return The final task status, finished or failed. */ public static TaskStatus waitForFinishOrFail( final Specifier<Facets> taskSpecifier, final TaskStatusHandler statusHandler, final TaskClient taskClient, final long pollingDelay, final String dn, final String wid ) { TaskStatus ts; String taskId = taskSpecifier.getUriMap().get( "taskId" ); ResponseEntity<TaskStatus> stat; boolean done = false; do { // queries the status of the task execution stat = taskClient.getStatus( taskId, dn, wid ); if(! HttpStatus.OK.equals( stat.getStatusCode() ) ) throw new RuntimeException( "Task::getStatus failed " + stat.getStatusCode().name() ); ts = stat.getBody(); // allows the implementor to do something with the task status statusHandler.handleStatus( ts ); try { Thread.sleep( pollingDelay ); } catch ( InterruptedException e ) { throw new RuntimeException( e ); } // finished without an error, good(?) if( finished( ts ) ) { // collect the result ResponseEntity<TaskResult> tr = null; try { tr = taskClient.getResult( taskId, dn, wid ); } catch( HttpClientErrorException e ) { if( 404 == e.getStatusCode().value() ) continue; } if(! HttpStatus.OK.equals( tr.getStatusCode() ) ) throw new RuntimeException( "Failed to obtain task result " + tr.getStatusCode().name() ); // do something with it statusHandler.handleResult( tr.getBody() ); done = true; } else if( failed( ts ) ) { // must be failed, not so good // find out way ResponseEntity<TaskFailure> tf = taskClient.getErrors( taskId, dn, wid ); if(! HttpStatus.OK.equals( tf.getStatusCode() ) ) throw new RuntimeException( "Failed to obtain task errors " + tf.getStatusCode().name() ); // handle the failure statusHandler.handleFailure( tf.getBody() ); done = true; } } while( ! done ); // run 'til the task hits a final state return ts; } /** * Same as the above method, but without a status handler. * * @param taskSpecifier The specifier of the task. * @param taskClient The task client, which should be used for polling. * @param pollingDelay The pollingDelay, its the delay between polling. * @param dn The user DN. * @param wid The workflow id. * * @return The final task status, finished or failed. */ public static TaskStatus waitForFinishOrFail( final Specifier<Facets> taskSpecifier, final TaskClient taskClient, final long pollingDelay, final String dn, final String wid ) { return waitForFinishOrFail( taskSpecifier, new LazyStatusHandler(), taskClient, pollingDelay,dn, wid ); } /** * Offers implementing clients the possibility to add values to the request context. * * The request context is used to create taskflows and the right place to provide * myProxyTokens. * * @param context The create request context. * @return The augmented context */ protected GNDMSResponseHeader setupContext( final GNDMSResponseHeader context ) { // example: context.addMyProxyToken( "c3grid", "foo", "bar" ); return context; } /** * @brief Allows the caller to select a quote. * * @param quotes All available quotes. * * @return The index of the accepted quote. \c null will disable * quote usage. */ protected abstract Integer selectQuote( List<Specifier<Quote>> quotes ); /** * @brief Allows additional handling for the task specifier. * * @param ts The task specifier, including all task facets as * payload. */ protected abstract void handleTaskSpecifier( Specifier<Facets> ts ); /** * @brief Handler for the task result. * * Override this method to gain access to the task(flow) result an * send it to the user, post process it or store it for later * usage. *@param res The result object. */ public abstract void handleResult( TaskResult res ); /** * @brief Handler for task failures. * * Override this method to gain access to the task(flow) error * object, e.g. to send an error-report to someone who cares. * * @param fail The failure object. */ public abstract void handleFailure( TaskFailure fail ); /** * @brief Checks if ts is FINISHED. * * @param ts The current task state. * * @return \c true if ts is FINISHED */ private static boolean finished( TaskStatus ts ) { return TaskStatus.Status.FINISHED.equals( ts.getStatus() ); } /** * @brief Checks if ts is FINISHED. * * @param ts The current task state. * * @return \c true if ts is FINISHED */ private static boolean failed( TaskStatus ts ) { return TaskStatus.Status.FAILED.equals( ts.getStatus() ); } /** * @brief Delivers the value of ::gorfxClient. * * @return The value of ::gorfxClient. */ public GORFXClient getGorfxClient() { return gorfxClient; } /** * @brief Sets the value of ::gorfxClient to \e gorfxClient. * * @param gorfxClient The new value of ::gorfxClient. */ public void setGorfxClient( GORFXClient gorfxClient ) { this.gorfxClient = gorfxClient; } /** * @brief Delivers the value of ::tfClient. * * @return The value of ::tfClient. */ public TaskFlowClient getTfClient() { return tfClient; } /** * @brief Sets the value of ::tfClient to \e tfClient. * * @param tfClient The new value of ::tfClient. */ public void setTfClient( TaskFlowClient tfClient ) { this.tfClient = tfClient; } /** * @brief Delivers the value of ::taskClient. * * @return The value of ::taskClient. */ public TaskClient getTaskClient() { return taskClient; } /** * @brief Sets the value of ::taskClient to \e taskClient. * * @param taskClient The new value of ::taskClient. */ public void setTaskClient( TaskClient taskClient ) { this.taskClient = taskClient; } /** * @brief Delivers the value of ::pollingDelay. * * @return The value of ::pollingDelay. */ public long getPollingDelay() { return pollingDelay; } /** * @brief Sets the value of ::pollingDelay to \e pollingDelay. * * @param pollingDelay The new value of ::pollingDelay. */ public void setPollingDelay( long pollingDelay ) { this.pollingDelay = pollingDelay; } public static class LazyStatusHandler implements TaskStatusHandler { @Override public void handleStatus( final TaskStatus stat ) { // this handler is lazy, it does nothing } @Override public void handleResult( final TaskResult body ) { // not required here } @Override public void handleFailure( final TaskFailure body ) { // not required here } } }