public void execute(Job job) {
   // Get the input as a String
   String data = ByteUtils.fromBytes(job.getData(), encoding);
   // Perform the reversal
   StringBuffer sb = new StringBuffer(data);
   sb = sb.reverse();
   // Store result as bytes
   job.setResult(ByteUtils.toBytes(sb.toString(), encoding));
   // Set the job state
   job.setState(JobState.COMPLETE);
 }
 /**
  * "\0REQ" == [ 00 52 45 51 ] == 5391697
  *
  * <p>"\0RES" == [ 00 52 45 53 ] == 5391699
  *
  * @param bytes array of bytes containing magic code for a packet
  * @throws BadMagicException if byte array is not 4 bytes long
  */
 public static GearmanPacketMagic fromBytes(byte[] bytes) {
   if (bytes != null && bytes.length == 4) {
     int magic = ByteUtils.fromBigEndian(bytes);
     if (magic == ByteUtils.fromBigEndian(REQ.toBytes())) {
       return REQ;
     }
     if (magic == ByteUtils.fromBigEndian(RES.toBytes())) {
       return RES;
     }
   }
   throw new BadMagicException(ByteUtils.toHex(bytes));
 }
 private GearmanPacket getPacketFromJob(GearmanJob job) {
   int destPos = 0;
   GearmanPacketMagic magic = GearmanPacketMagic.REQ;
   GearmanPacketType type = null;
   byte[] packetdata = null;
   byte[] fnname = ByteUtils.toAsciiBytes(job.getFunctionName());
   byte[] uid = job.getID();
   byte[] data = job.getData();
   if (job.getPriority().equals(GearmanJob.JobPriority.HIGH)) {
     type =
         job.isBackgroundJob()
             ? GearmanPacketType.SUBMIT_JOB_HIGH_BG
             : GearmanPacketType.SUBMIT_JOB_HIGH;
   }
   if (job.getPriority().equals(GearmanJob.JobPriority.LOW)) {
     type =
         job.isBackgroundJob()
             ? GearmanPacketType.SUBMIT_JOB_LOW_BG
             : GearmanPacketType.SUBMIT_JOB_LOW;
   }
   if (job.getPriority().equals(GearmanJob.JobPriority.NORMAL)) {
     type = job.isBackgroundJob() ? GearmanPacketType.SUBMIT_JOB_BG : GearmanPacketType.SUBMIT_JOB;
   }
   packetdata = new byte[fnname.length + uid.length + data.length + 2];
   System.arraycopy(fnname, 0, packetdata, destPos, fnname.length);
   destPos += fnname.length;
   packetdata[destPos++] = ByteUtils.NULL;
   System.arraycopy(uid, 0, packetdata, destPos, uid.length);
   destPos += uid.length;
   packetdata[destPos++] = ByteUtils.NULL;
   System.arraycopy(data, 0, packetdata, destPos, data.length);
   return new GearmanPacketImpl(magic, type, packetdata);
 }
 public byte[] digest(byte[] input) {
   String function = DigestFunction.class.getCanonicalName();
   String uniqueId = null;
   byte[] algoNameBytes = ALGO_NAME.getBytes();
   byte[] digestParms = new byte[input.length + algoNameBytes.length + 1];
   System.arraycopy(algoNameBytes, 0, digestParms, 0, algoNameBytes.length);
   digestParms[algoNameBytes.length] = '\0';
   System.arraycopy(input, 0, digestParms, algoNameBytes.length + 1, input.length);
   GearmanJob job = GearmanJobImpl.createJob(function, digestParms, uniqueId);
   Future<GearmanJobResult> f = client.submit(job);
   GearmanJobResult jr = null;
   try {
     jr = f.get();
   } catch (Exception e) {
     e.printStackTrace(); // NOPMD
   }
   if (jr == null) {
     return new byte[0];
   } else {
     if (jr.jobSucceeded()) {
       return jr.getResults();
     } else {
       System.err.println(ByteUtils.fromUTF8Bytes(jr.getExceptions())); // NOPMD
       return new byte[0];
     }
   }
 }
 private void addNewJob(GearmanSessionEvent event) {
   byte[] handle, data, functionNameBytes, unique;
   GearmanPacket p = event.getPacket();
   GearmanJobServerSession sess = event.getSession();
   String functionName;
   handle = p.getDataComponentValue(GearmanPacket.DataComponentName.JOB_HANDLE);
   functionNameBytes = p.getDataComponentValue(GearmanPacket.DataComponentName.FUNCTION_NAME);
   data = p.getDataComponentValue(GearmanPacket.DataComponentName.DATA);
   unique = p.getDataComponentValue(DataComponentName.UNIQUE_ID);
   functionName = ByteUtils.fromUTF8Bytes(functionNameBytes);
   FunctionDefinition def = functionMap.get(functionName);
   if (def == null) {
     GearmanTask gsr =
         new GearmanTask(
             new GearmanPacketImpl(GearmanPacketMagic.REQ, GearmanPacketType.WORK_FAIL, handle));
     sess.submitTask(gsr);
   } else {
     GearmanFunction function = def.getFactory().getFunction();
     function.setData(data);
     function.setJobHandle(handle);
     function.registerEventListener(sess);
     if (unique != null && unique.length > 0) {
       function.setUniqueId(unique);
     }
     functionList.add(function);
   }
 }
  public boolean addServer(GearmanJobServerConnection conn)
      throws IllegalArgumentException, IllegalStateException {

    if (conn == null) {
      throw new IllegalArgumentException("Connection can not be null");
    }
    // this is a sub-optimal way to look for dups, but addJobServer
    // ops should be infrequent enough that this should be a big penalty
    for (GearmanJobServerSession sess : sessionMap.values()) {
      if (sess.getConnection().equals(conn)) {
        return true;
      }
    }

    GearmanJobServerSession session = new GearmanJobServerSession(conn);
    if (ioAvailable == null) {
      try {
        ioAvailable = Selector.open();
      } catch (IOException ioe) {
        LOG.warn("Failed to connect to job server " + conn + ".", ioe);
        return false;
      }
    }
    try {
      session.initSession(ioAvailable, this);
    } catch (IOException ioe) {
      LOG.warn("Failed to initialize session with job server " + conn + ".", ioe);
      return false;
    }
    SelectionKey key = session.getSelectionKey();
    if (key == null) {
      String msg =
          "Session "
              + session
              + " has a null "
              + "selection key. Server will not be added to worker.";
      LOG.warn(msg);
      throw new IllegalStateException(msg);
    }
    sessionMap.put(key, session);

    GearmanPacket p =
        new GearmanPacketImpl(
            GearmanPacketMagic.REQ, GearmanPacketType.SET_CLIENT_ID, ByteUtils.toUTF8Bytes(id));
    session.submitTask(new GearmanTask(p));

    for (FunctionDefinition def : functionMap.values()) {
      p = generateCanDoPacket(def);
      session.submitTask(new GearmanTask(p)); // NOPMD
    }

    p = new GearmanPacketImpl(GearmanPacketMagic.REQ, getGrabJobPacketType(), new byte[0]);
    GearmanTask gsr = new GearmanTask(new GrabJobEventHandler(session), p);
    taskMap.put(session, gsr);
    session.submitTask(gsr);

    LOG.debug("Added server " + conn + " to worker " + this);
    return true;
  }
  private GearmanPacket generateCanDoPacket(FunctionDefinition def) {
    GearmanPacketType pt = GearmanPacketType.CAN_DO;
    byte[] data = null;
    byte[] name = ByteUtils.toUTF8Bytes(def.getFactory().getFunctionName());
    long timeout = def.getTimeout();

    if (timeout > 0) {
      pt = GearmanPacketType.CAN_DO_TIMEOUT;
      byte[] to = ByteUtils.toUTF8Bytes(String.valueOf(timeout));
      data = new byte[name.length + to.length + 1];
      System.arraycopy(name, 0, data, 0, name.length);
      data[name.length] = ByteUtils.NULL;
      System.arraycopy(to, 0, data, name.length + 1, to.length);
    } else {
      data = name;
    }
    return new GearmanPacketImpl(GearmanPacketMagic.REQ, pt, data);
  }
 public void unregisterFunction(String functionName) {
   functionMap.remove(functionName);
   sendToAll(
       new GearmanPacketImpl(
           GearmanPacketMagic.REQ,
           GearmanPacketType.CANT_DO,
           ByteUtils.toUTF8Bytes(functionName)));
   LOG.debug("Worker " + this + " has unregistered function " + functionName);
 }
 public void setWorkerID(String id) throws IllegalArgumentException {
   if (id == null) {
     throw new IllegalArgumentException("Worker ID may not be null");
   }
   this.id = id;
   sendToAll(
       new GearmanPacketImpl(
           GearmanPacketMagic.REQ, GearmanPacketType.SET_CLIENT_ID, ByteUtils.toUTF8Bytes(id)));
 }
 public static void main(String[] args) {
   if (args.length == 0 || args.length > 3) {
     usage();
     return;
   }
   String host = Constants.GEARMAN_DEFAULT_TCP_HOST;
   int port = Constants.GEARMAN_DEFAULT_TCP_PORT;
   byte[] payload = ByteUtils.toUTF8Bytes(args[args.length - 1]);
   for (String arg : args) {
     if (arg.startsWith("-h")) {
       host = arg.substring(2);
     } else if (arg.startsWith("-p")) {
       port = Integer.parseInt(arg.substring(2));
     }
   }
   DigestClient dc = new DigestClient(host, port);
   byte[] md5 = dc.digest(payload);
   System.out.println(ByteUtils.toHex(md5)); // NOPMD
   dc.shutdown();
 }
 public byte[] echo(byte[] data) throws IOException, GearmanException {
   if (!runState.equals(state.RUNNING)) {
     throw new IllegalStateException(CLIENT_NOT_ACTIVE);
   }
   GearmanPacket echoRequest =
       new GearmanPacketImpl(GearmanPacketMagic.REQ, GearmanPacketType.ECHO_REQ, data);
   GearmanEchoResponseHandler handler = new GearmanEchoResponseHandler();
   GearmanTask t = new GearmanTask(handler, echoRequest);
   GearmanJobServerSession session = getSessionForTask();
   session.submitTask(t);
   LOG.info(
       "Client "
           + this
           + " has submitted echo request "
           + "(payload = "
           + ByteUtils.toHex(data)
           + " to session "
           + session);
   if (!driveRequestTillState(t, GearmanTask.State.FINISHED)) {
     throw new GearmanException("Failed to execute echo request " + t + " to session " + session);
   }
   LOG.info("Client " + this + " has completed echo request " + "to session " + session);
   return handler.getResults();
 }
 private GearmanPacketType(int i) {
   type = ByteUtils.toBigEndian(i);
   code = i;
 }
/*
 * ISSUES/RFEs
 * -several of the invoke/submit/execute methods throw OpNotSupported exception
 *  because they rely task cancelation, which we dont support
 * -shutdownNow will always return an empty set, there is a few problems here
 *  1) a gearmanJob is a callable, not a runnable
 *  2) we track requests, not jobs inside the session object
 * -selectUpdate should provide the ability to on select on certain events
 */
public class GearmanClientImpl implements GearmanClient, GearmanSessionEventHandler {

  private static enum state {
    RUNNING,
    SHUTTINGDOWN,
    TERMINATED
  }

  private static final String DESCRIPION_PREFIX = "GearmanClient";
  private static final org.slf4j.Logger LOG =
      org.slf4j.LoggerFactory.getLogger(Constants.GEARMAN_CLIENT_LOGGER_NAME);
  private static final String CLIENT_NOT_ACTIVE = "Client is not active";
  private static final long DEFAULT_DRIVE_REQUEST_TIMEOUT = 2000;
  private static final byte[] EXCEPTIONS = ByteUtils.toAsciiBytes("exceptions");
  private final String DESCRIPTION;
  private Map<SelectionKey, GearmanJobServerSession> sessionsMap = null;
  private Selector ioAvailable = null;
  private state runState = state.RUNNING;
  private final Map<GearmanJobServerSession, Map<JobHandle, GearmanJobImpl>> sessionJobsMap;
  private GearmanJobImpl jobAwatingCreation;
  private final Timer timer = new Timer();
  private final GearmanJobServerIpConnectionFactory connFactory =
      new GearmanNIOJobServerConnectionFactory();
  private final long driveRequestTimeout;

  private static class Alarm extends TimerTask {

    private final AtomicBoolean timesUp = new AtomicBoolean(false);

    @Override
    public void run() {
      timesUp.set(true);
    }

    public boolean hasFired() {
      return timesUp.get();
    }
  }

  private static class JobHandle {

    private final byte[] jobHandle;

    JobHandle(GearmanJobImpl job) {
      this(job.getHandle());
    }

    JobHandle(byte[] handle) {
      jobHandle = new byte[handle.length];
      System.arraycopy(handle, 0, jobHandle, 0, handle.length);
    }

    @Override
    public boolean equals(Object that) {
      if (that == null) {
        return false;
      }
      if (!(that instanceof JobHandle)) {
        return false;
      }
      JobHandle thatHandle = (JobHandle) that;
      return Arrays.equals(jobHandle, thatHandle.jobHandle);
    }

    @Override
    public int hashCode() {
      return Arrays.hashCode(jobHandle);
    }
  }

  /**
   * Create a new GearmanClient instance. Instances are not thread-safe and should not be shared
   * across threads.
   */
  public GearmanClientImpl() {
    this(DEFAULT_DRIVE_REQUEST_TIMEOUT);
  }

  public GearmanClientImpl(long driveRequestTimeout) {
    if (driveRequestTimeout < 0) {
      throw new IllegalArgumentException("Drive request timeout must be 0" + " or greater.");
    }
    sessionsMap = new Hashtable<SelectionKey, GearmanJobServerSession>();
    sessionJobsMap = new HashMap<GearmanJobServerSession, Map<JobHandle, GearmanJobImpl>>();
    DESCRIPTION = DESCRIPION_PREFIX + ":" + Thread.currentThread().getId();
    this.driveRequestTimeout = driveRequestTimeout;
  }

  public boolean addJobServer(String host, int port) {
    return addJobServer(host, port, true);
  }

  // TODO move to interface?
  public boolean addJobServer(String host, int port, boolean serverForwardsExceptions) {
    return addJobServer(connFactory.createConnection(host, port), serverForwardsExceptions);
  }

  public boolean addJobServer(GearmanJobServerConnection newconn)
      throws IllegalArgumentException, IllegalStateException {
    return addJobServer(newconn, true);
  }

  public boolean addJobServer(GearmanJobServerConnection newconn, boolean serverForwardsExceptions)
      throws IllegalArgumentException, IllegalStateException {

    // TODO remove this restriction
    if (!(newconn instanceof GearmanNIOJobServerConnection)) {
      throw new IllegalArgumentException(
          "Client currently only "
              + "supports "
              + GearmanNIOJobServerConnection.class.getName()
              + " connections.");
    }

    GearmanNIOJobServerConnection conn = (GearmanNIOJobServerConnection) newconn;

    if (!runState.equals(state.RUNNING)) {
      throw new RejectedExecutionException("Client has been shutdown");
    }

    GearmanJobServerSession session = new GearmanJobServerSession(conn);
    if (sessionsMap.values().contains(session)) {
      LOG.debug(
          "The server "
              + newconn
              + " was previously "
              + "added to the client. Ignoring add request.");
      return true;
    }

    try {
      if (ioAvailable == null) {
        ioAvailable = Selector.open();
      }

      session.initSession(ioAvailable, this);
      SelectionKey key = session.getSelectionKey();
      sessionsMap.put(key, session);
    } catch (IOException ioe) {
      LOG.warn("Failed to connect to job server " + newconn + ".", ioe);
      return false;
    }
    if (serverForwardsExceptions && !setForwardExceptions(session)) {
      return false;
    }
    sessionJobsMap.put(session, new HashMap<JobHandle, GearmanJobImpl>());

    LOG.info("Added connection " + conn + " to client " + this);
    return true;
  }

  public boolean hasConnection(GearmanJobServerConnection conn) {
    for (GearmanJobServerSession sess : sessionsMap.values()) {
      if (sess.getConnection().equals(conn)) {
        return true;
      }
    }
    return false;
  }

  public List<GearmanJobServerConnection> getSetOfJobServers() throws IllegalStateException {
    if (!runState.equals(state.RUNNING)) {
      throw new IllegalStateException(CLIENT_NOT_ACTIVE);
    }

    ArrayList<GearmanJobServerConnection> retSet = new ArrayList<GearmanJobServerConnection>();
    for (GearmanJobServerSession sess : sessionsMap.values()) {
      retSet.add(sess.getConnection());
    }
    return retSet;
  }

  public void removeJobServer(GearmanJobServerConnection conn)
      throws IllegalArgumentException, IllegalStateException {
    if (!runState.equals(state.RUNNING)) {
      throw new IllegalStateException(
          "JobServers can not be removed " + "once shutdown has been commenced.");
    }

    // TODO, make this better
    Iterator<GearmanJobServerSession> iter = sessionsMap.values().iterator();
    GearmanJobServerSession session = null;
    boolean foundit = false;
    while (iter.hasNext() && !foundit) {
      session = iter.next();
      if (session.getConnection().equals(conn)) {
        foundit = true;
      }
    }

    if (!foundit) {
      throw new IllegalArgumentException(
          "JobServer " + conn + " has not" + " been registered with this client.");
    }

    shutDownSession(session);
    LOG.info("Removed job server " + conn + " from client " + this);
  }

  public <T> Future<T> submit(Callable<T> task) {

    if (task == null) {
      throw new IllegalStateException("Null task was submitted to " + "gearman client");
    }

    if (!runState.equals(state.RUNNING)) {
      throw new RejectedExecutionException("Client has been shutdown");
    }

    if (!(task instanceof GearmanServerResponseHandler)) {
      throw new RejectedExecutionException(
          "Task must implement the "
              + GearmanServerResponseHandler.class
              + " interface to"
              + " submitted to this client");
    }

    GearmanJobImpl job = (GearmanJobImpl) task;
    GearmanServerResponseHandler handler = (GearmanServerResponseHandler) job;

    if (job.isDone()) {
      throw new RejectedExecutionException("Task can not be resubmitted ");
    }

    GearmanJobServerSession session = null;
    try {
      session = getSessionForTask();
    } catch (IOException ioe) {
      throw new RejectedExecutionException(ioe);
    }
    job.setJobServerSession(session);
    GearmanPacket submitRequest = getPacketFromJob(job);
    GearmanTask submittedJob = new GearmanTask(handler, submitRequest);
    session.submitTask(submittedJob);
    LOG.info(
        "Client "
            + this
            + " has submitted job "
            + job
            + // NOPMD
            " to session "
            + session
            + ". Job has been added to the "
            + // NOPMD
            "active job queue");
    try {
      jobAwatingCreation = job;
      if (!(driveRequestTillState(submittedJob, GearmanTask.State.RUNNING))) {
        throw new RejectedExecutionException(
            "Timed out waiting for" + " submission of " + job + " to complete");
      }
    } catch (IOException ioe) {
      LOG.warn("Client " + this + " encounted an " + "IOException while drivingIO", ioe);
      throw new RejectedExecutionException(
          "Failed to successfully " + "submit" + job + " due to IOException", ioe);
    } finally {
      jobAwatingCreation = null;
    }

    return (Future<T>) job;
  }

  public <T> Future<T> submit(Runnable task, T result) {
    throw new UnsupportedOperationException(
        "Client does not support " + "execution of non-GearmanJob objects");
  }

  public Future<?> submit(Runnable task) {
    throw new UnsupportedOperationException(
        "Client does not support " + "execution of non-GearmanJob objects");
  }

  public void execute(Runnable command) {
    throw new UnsupportedOperationException(
        "Client does not support " + "execution of non-GearmanJob objects");
  }

  // NOTE, there is a subtle difference between the ExecutorService invoke*
  // method signatures in jdk1.5 and jdk1.6 that requires we implement these
  // methods in their 'erasure' format (that is without the use of the
  // specified generic types), otherwise we will not be able to compile this
  // class using both compilers.

  @SuppressWarnings("unchecked") // NOPMD
  public List invokeAll(Collection tasks) throws InterruptedException {
    ArrayList<Future> futures = new ArrayList<Future>();
    Iterator<Callable<Future>> iter = tasks.iterator();
    while (iter.hasNext()) {
      Callable<Future> curTask = iter.next();
      futures.add(this.submit(curTask));
    }
    for (Future results : futures) {
      try {
        results.get();
      } catch (ExecutionException ee) {
        LOG.warn("Failed to execute task " + results + ".", ee);
      }
    }
    return futures;
  }

  @SuppressWarnings("unchecked")
  public List invokeAll(Collection tasks, long timeout, TimeUnit unit) throws InterruptedException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  @SuppressWarnings("unchecked")
  public Future invokeAny(Collection tasks) throws InterruptedException, ExecutionException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  @SuppressWarnings("unchecked")
  public Future invokeAny(Collection tasks, long timeout, TimeUnit unit)
      throws InterruptedException, ExecutionException, TimeoutException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public GearmanJobStatus getJobStatus(GearmanJob job)
      throws IOException, GearmanException, IllegalStateException {
    // we need to know what session to ask that information can not be
    // gleened from the job object.
    if (!(job instanceof GearmanJobImpl)) {
      throw new IllegalArgumentException("job must be of type " + GearmanJobImpl.class);
    }
    GearmanJobImpl jobImpl = (GearmanJobImpl) job;
    return updateJobStatus(jobImpl.getHandle(), jobImpl.getSession());
  }

  public byte[] echo(byte[] data) throws IOException, GearmanException {
    if (!runState.equals(state.RUNNING)) {
      throw new IllegalStateException(CLIENT_NOT_ACTIVE);
    }
    GearmanPacket echoRequest =
        new GearmanPacketImpl(GearmanPacketMagic.REQ, GearmanPacketType.ECHO_REQ, data);
    GearmanEchoResponseHandler handler = new GearmanEchoResponseHandler();
    GearmanTask t = new GearmanTask(handler, echoRequest);
    GearmanJobServerSession session = getSessionForTask();
    session.submitTask(t);
    LOG.info(
        "Client "
            + this
            + " has submitted echo request "
            + "(payload = "
            + ByteUtils.toHex(data)
            + " to session "
            + session);
    if (!driveRequestTillState(t, GearmanTask.State.FINISHED)) {
      throw new GearmanException("Failed to execute echo request " + t + " to session " + session);
    }
    LOG.info("Client " + this + " has completed echo request " + "to session " + session);
    return handler.getResults();
  }

  public int getNumberofActiveJobs() throws IllegalStateException {
    if (runState.equals(state.TERMINATED)) {
      throw new IllegalStateException(CLIENT_NOT_ACTIVE);
    }
    int size = 0;

    for (Map<JobHandle, GearmanJobImpl> cur : sessionJobsMap.values()) {
      size += cur.size();
    }
    return size;
  }

  public void handleSessionEvent(GearmanSessionEvent event)
      throws IllegalArgumentException, IllegalStateException {
    GearmanPacket p = event.getPacket();
    GearmanJobServerSession s = event.getSession();
    GearmanPacketType t = p.getPacketType();
    Map<JobHandle, GearmanJobImpl> jobsMaps = sessionJobsMap.get(s);

    switch (t) {
      case JOB_CREATED:
        if (jobAwatingCreation == null) {
          throw new IllegalStateException(
              "Recevied job creation " + "message but have not job awaiting submission.");
        }
        if (!jobAwatingCreation.isBackgroundJob()) {
          jobsMaps.put(new JobHandle(jobAwatingCreation), jobAwatingCreation);
        }
        break;
      case WORK_DATA:
      case WORK_STATUS:
      case WORK_WARNING:
      case WORK_COMPLETE:
      case WORK_FAIL:
      case WORK_EXCEPTION:
        JobHandle handle =
            new JobHandle(p.getDataComponentValue(GearmanPacket.DataComponentName.JOB_HANDLE));
        GearmanJobImpl job = jobsMaps.get(handle);
        if (job == null) {
          LOG.warn(
              "Client received packet from server"
                  + " for unknown job ( job_handle = "
                  + handle
                  + " packet = "
                  + t
                  + " )");
        } else {
          job.handleEvent(p);
          if (job.isDone()) {
            jobsMaps.remove(handle);
          }
        }
        break;
      case ERROR:
        String errCode =
            ByteUtils.fromUTF8Bytes(
                p.getDataComponentValue(GearmanPacket.DataComponentName.ERROR_CODE));
        String errMsg =
            ByteUtils.fromUTF8Bytes(
                p.getDataComponentValue(GearmanPacket.DataComponentName.ERROR_TEXT));
        LOG.warn(
            "Received error code "
                + errCode
                + "( "
                + errMsg
                + " )"
                + " from session "
                + s
                + ". Shutting session down");
        shutDownSession(s);
        if (sessionsMap.isEmpty()) {
          shutdown();
        }
        break;
      default:
        LOG.warn(
            "received un-expected packet from Job"
                + " Server Session: "
                + p
                + ". Shutting down session");
        shutDownSession(s);
        if (sessionsMap.isEmpty()) {
          shutdown();
        }
    }
  }

  public void shutdown() {
    if (!runState.equals(state.RUNNING)) {
      return;
    }
    runState = state.SHUTTINGDOWN;
    LOG.info("Commencing controlled shutdown of client: " + this);
    try {
      awaitTermination(-1, TimeUnit.SECONDS);
    } catch (InterruptedException ie) {
      LOG.info("Client shutdown interrupted while waiting" + " for jobs to terminate.");
    }
    shutdownNow();
    LOG.info("Completed ontrolled shutdown of client: " + this);
  }

  public List<Runnable> shutdownNow() {
    runState = state.SHUTTINGDOWN;
    LOG.info("Commencing immediate shutdown of client: " + this);
    timer.cancel();
    Iterator<GearmanJobServerSession> sessions = sessionsMap.values().iterator();
    while (sessions.hasNext()) {
      GearmanJobServerSession curSession = sessions.next();
      if (!curSession.isInitialized()) {
        continue;
      }
      try {
        curSession.closeSession();
      } catch (Exception e) {
        LOG.warn(
            "Failed to closes session "
                + curSession
                + " while performing immediate shutdown of client "
                + this
                + ". Encountered the following exception "
                + e);
      }
      sessions.remove();
    }
    sessionsMap.clear();
    sessionsMap = null;
    runState = state.TERMINATED;
    try {
      ioAvailable.close();
    } catch (IOException ioe) {
      LOG.warn("Encountered IOException while closing selector for client ", ioe);
    }
    LOG.info("Completed shutdown of client: " + this);
    return new ArrayList<Runnable>();
  }

  public boolean isShutdown() {
    return !runState.equals(state.RUNNING);
  }

  public boolean isTerminated() {
    return runState.equals(state.TERMINATED);
  }

  public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
    TimeUnit sessionUnit = TimeUnit.MILLISECONDS;
    long timeLeft = -1;
    long timeOutInMills =
        timeout < 0
            ? -1
            : TimeUnit.MILLISECONDS.convert(timeout, unit) + System.currentTimeMillis();

    if (getNumberofActiveJobs() == 0) {
      return true;
    }

    for (GearmanJobServerSession curSession : sessionsMap.values()) {
      if (!(curSession.isInitialized())) {
        continue;
      }
      // negative timeout means block till completion
      if (timeout >= 0) {
        timeLeft = timeOutInMills - System.currentTimeMillis();
        if (timeLeft <= 0) {
          LOG.warn("awaitTermination exceeded timeout.");
          break;
        }
      }
      try {
        curSession.waitForTasksToComplete(timeLeft, sessionUnit);
      } catch (TimeoutException te) {
        LOG.info("timed out waiting for all tasks to complete");
        break;
      }
    }
    return getNumberofActiveJobs() == 0;
  }

  @Override
  public String toString() {
    return DESCRIPTION;
  }

  private void driveClientIO() throws IOException, GearmanException {
    for (GearmanJobServerSession sess : sessionsMap.values()) {
      int interestOps = SelectionKey.OP_READ;
      if (sess.sessionHasDataToWrite()) {
        interestOps |= SelectionKey.OP_WRITE;
      }
      try {
        sess.getSelectionKey().interestOps(interestOps);
      } catch (IllegalStateException ise) {
        LOG.warn("Unable to drive IO for session " + sess + "," + " skipping.", ise);
        continue;
      }
    }
    ioAvailable.selectNow();
    Set<SelectionKey> keys = ioAvailable.selectedKeys();
    LOG.trace(
        "Driving IO for client "
            + this
            + ". "
            + keys.size()
            + " session(s) currently available for IO");
    Iterator<SelectionKey> iter = keys.iterator();
    while (iter.hasNext()) {
      SelectionKey key = iter.next();
      GearmanJobServerSession s = sessionsMap.get(key);
      s.driveSessionIO();
    }
  }

  private GearmanJobStatus updateJobStatus(byte[] jobhandle, GearmanJobServerSession session)
      throws IOException, IllegalStateException, GearmanException {
    if (!runState.equals(state.RUNNING)) {
      throw new IllegalStateException(CLIENT_NOT_ACTIVE);
    }

    if (jobhandle == null || jobhandle.length == 0) {
      throw new IllegalStateException("Invalid job handle. Handle must" + " not be null nor empty");
    }
    GearmanPacket statusRequest =
        new GearmanPacketImpl(GearmanPacketMagic.REQ, GearmanPacketType.GET_STATUS, jobhandle);
    GearmanServerResponseHandler handler =
        (GearmanServerResponseHandler) new GearmanJobStatusImpl();
    GearmanTask t = new GearmanTask(handler, statusRequest);
    session.submitTask(t);
    if (!driveRequestTillState(t, GearmanTask.State.FINISHED)) {
      throw new GearmanException(
          "Failed to execute jobstatus request " + t + " to session " + session);
    }
    return (GearmanJobStatus) handler;
  }

  private GearmanJobServerSession getSessionForTask() throws IOException {
    if (sessionsMap.values().isEmpty()) {
      throw new IOException("No servers registered with client");
    }

    ArrayList<GearmanJobServerSession> sessions = new ArrayList<GearmanJobServerSession>();
    sessions.addAll(sessionsMap.values());
    Random rand = new Random(System.currentTimeMillis());
    int s = rand.nextInt(sessions.size());
    GearmanJobServerSession session = sessions.get(s);
    IOException ioe = new IOException("No servers available for client");
    for (int i = 0; i < sessions.size(); i++) {
      if (!session.isInitialized()) {
        try {
          session.initSession(ioAvailable, this);
          sessionsMap.put(session.getSelectionKey(), session);
        } catch (Exception e) {
          if (sessions.size() > 1) {
            // try next one
            int prev = s;
            s = (s + 1) % sessions.size();
            session = sessions.get(s);
            LOG.warn(
                "Got exception attempting to retrieve "
                    + "session for task. Try next server at "
                    + "pos={}, previous pos={}",
                s,
                prev);
            continue;
          } else {
            break;
          }
        }
      }
      return session;
    }
    throw ioe;
  }

  private boolean driveRequestTillState(GearmanTask r, GearmanTask.State state)
      throws IOException, GearmanException {
    Alarm alarm = new Alarm();
    timer.schedule(alarm, driveRequestTimeout);
    while (r.getState().compareTo(state) < 0 && !(alarm.hasFired())) {
      driveClientIO();
    }
    return r.getState().compareTo(state) >= 0;
  }

  private GearmanPacket getPacketFromJob(GearmanJob job) {
    int destPos = 0;
    GearmanPacketMagic magic = GearmanPacketMagic.REQ;
    GearmanPacketType type = null;
    byte[] packetdata = null;
    byte[] fnname = ByteUtils.toAsciiBytes(job.getFunctionName());
    byte[] uid = job.getID();
    byte[] data = job.getData();
    if (job.getPriority().equals(GearmanJob.JobPriority.HIGH)) {
      type =
          job.isBackgroundJob()
              ? GearmanPacketType.SUBMIT_JOB_HIGH_BG
              : GearmanPacketType.SUBMIT_JOB_HIGH;
    }
    if (job.getPriority().equals(GearmanJob.JobPriority.LOW)) {
      type =
          job.isBackgroundJob()
              ? GearmanPacketType.SUBMIT_JOB_LOW_BG
              : GearmanPacketType.SUBMIT_JOB_LOW;
    }
    if (job.getPriority().equals(GearmanJob.JobPriority.NORMAL)) {
      type = job.isBackgroundJob() ? GearmanPacketType.SUBMIT_JOB_BG : GearmanPacketType.SUBMIT_JOB;
    }
    packetdata = new byte[fnname.length + uid.length + data.length + 2];
    System.arraycopy(fnname, 0, packetdata, destPos, fnname.length);
    destPos += fnname.length;
    packetdata[destPos++] = ByteUtils.NULL;
    System.arraycopy(uid, 0, packetdata, destPos, uid.length);
    destPos += uid.length;
    packetdata[destPos++] = ByteUtils.NULL;
    System.arraycopy(data, 0, packetdata, destPos, data.length);
    return new GearmanPacketImpl(magic, type, packetdata);
  }

  private void shutDownSession(GearmanJobServerSession s) {
    if (s.isInitialized()) {
      SelectionKey k = s.getSelectionKey();
      if (k != null) {
        sessionsMap.remove(k);
        k.cancel();
      }
      s.closeSession();
    }
    sessionJobsMap.remove(s);
  }

  private boolean setForwardExceptions(GearmanJobServerSession session) {
    GearmanServerResponseHandler optionReqHandler =
        new GearmanServerResponseHandler() {
          boolean isDone = false;

          public boolean isDone() {
            return isDone;
          }

          public void handleEvent(GearmanPacket event) throws GearmanException {
            GearmanPacketType type = event.getPacketType();
            if (type.equals(GearmanPacketType.OPTION_RES)
                && Arrays.equals(EXCEPTIONS, event.getData())) {
              isDone = true;
            }
          }
        };
    GearmanTask setExceptionsTask =
        new GearmanTask(
            optionReqHandler,
            new GearmanPacketImpl(
                GearmanPacketMagic.REQ, GearmanPacketType.OPTION_REQ, EXCEPTIONS));
    session.submitTask(setExceptionsTask);
    Exception e = null;
    boolean success = false;
    try {
      success = driveRequestTillState(setExceptionsTask, GearmanTask.State.FINISHED);
    } catch (IOException ioe) {
      e = ioe;
    }
    if (!success) {
      if (e != null) {
        LOG.info("Failed to set forward-exceptions option to " + session.getConnection(), e);
      } else {
        LOG.info("Failed to set forward-exceptions option to " + session.getConnection());
      }
      return false;
    }
    return true;
  }
}
  public void handleSessionEvent(GearmanSessionEvent event)
      throws IllegalArgumentException, IllegalStateException {
    GearmanPacket p = event.getPacket();
    GearmanJobServerSession s = event.getSession();
    GearmanPacketType t = p.getPacketType();
    Map<JobHandle, GearmanJobImpl> jobsMaps = sessionJobsMap.get(s);

    switch (t) {
      case JOB_CREATED:
        if (jobAwatingCreation == null) {
          throw new IllegalStateException(
              "Recevied job creation " + "message but have not job awaiting submission.");
        }
        if (!jobAwatingCreation.isBackgroundJob()) {
          jobsMaps.put(new JobHandle(jobAwatingCreation), jobAwatingCreation);
        }
        break;
      case WORK_DATA:
      case WORK_STATUS:
      case WORK_WARNING:
      case WORK_COMPLETE:
      case WORK_FAIL:
      case WORK_EXCEPTION:
        JobHandle handle =
            new JobHandle(p.getDataComponentValue(GearmanPacket.DataComponentName.JOB_HANDLE));
        GearmanJobImpl job = jobsMaps.get(handle);
        if (job == null) {
          LOG.warn(
              "Client received packet from server"
                  + " for unknown job ( job_handle = "
                  + handle
                  + " packet = "
                  + t
                  + " )");
        } else {
          job.handleEvent(p);
          if (job.isDone()) {
            jobsMaps.remove(handle);
          }
        }
        break;
      case ERROR:
        String errCode =
            ByteUtils.fromUTF8Bytes(
                p.getDataComponentValue(GearmanPacket.DataComponentName.ERROR_CODE));
        String errMsg =
            ByteUtils.fromUTF8Bytes(
                p.getDataComponentValue(GearmanPacket.DataComponentName.ERROR_TEXT));
        LOG.warn(
            "Received error code "
                + errCode
                + "( "
                + errMsg
                + " )"
                + " from session "
                + s
                + ". Shutting session down");
        shutDownSession(s);
        if (sessionsMap.isEmpty()) {
          shutdown();
        }
        break;
      default:
        LOG.warn(
            "received un-expected packet from Job"
                + " Server Session: "
                + p
                + ". Shutting down session");
        shutDownSession(s);
        if (sessionsMap.isEmpty()) {
          shutdown();
        }
    }
  }
  public void work() {
    if (!state.equals(State.IDLE)) {
      throw new IllegalStateException(
          "Can not call work while worker " + "is running or shutting down");
    }

    state = State.RUNNING;
    // a map keeping track of sessions with connection errors
    // (to avoid printing an error about them in every reconnect attempt)
    Map<GearmanJobServerSession, Boolean> havingConnectionError =
        new HashMap<GearmanJobServerSession, Boolean>();

    while (isRunning()) {

      // look for sessions which have been disconnected and attempt to reconnect.
      for (Iterator<GearmanJobServerSession> iter = sessionMap.values().iterator();
          iter.hasNext(); ) {
        GearmanJobServerSession sess = iter.next();
        if (!sess.isInitialized()) {
          try {

            // reconnect, unregister old selection key and register new one
            SelectionKey oldKey = sess.isInitialized() ? sess.getSelectionKey() : null;
            sess.initSession(ioAvailable, this);
            if (oldKey != null) {
              iter.remove();
            }
            sessionMap.put(sess.getSelectionKey(), sess);

            // register all functions with the newly reconnected server
            for (FunctionDefinition d : functionMap.values()) {
              GearmanTask gsr = new GearmanTask(null, generateCanDoPacket(d));
              sess.submitTask(gsr);
            }

            GearmanPacket p =
                new GearmanPacketImpl(
                    GearmanPacketMagic.REQ,
                    GearmanPacketType.SET_CLIENT_ID,
                    ByteUtils.toUTF8Bytes(id));
            sess.submitTask(new GearmanTask(p));

            GearmanTask sessTask =
                new GearmanTask(
                    new GrabJobEventHandler(sess),
                    new GearmanPacketImpl(
                        GearmanPacketMagic.REQ, getGrabJobPacketType(), new byte[0]));
            sess.submitTask(sessTask);
            sess.driveSessionIO();

            // log reconnection message
            if (havingConnectionError.get(sess)) {
              LOG.info("Re-established connection to " + sess.getConnection().toString());
            }
            havingConnectionError.put(sess, false);
          } catch (IOException e) {
            if (!havingConnectionError.get(sess)) {
              LOG.warn("Error connecting to " + sess + ", will keep trying..");
            }

            havingConnectionError.put(sess, true);

            try {
              Thread.sleep(50);
            } catch (InterruptedException e1) {
            }
          }
        } else {
          havingConnectionError.put(sess, false);
        }
      }

      for (GearmanJobServerSession sess : sessionMap.values()) {
        // if still disconnected, skip
        if (!sess.isInitialized()) {
          continue;
        }
        int interestOps = SelectionKey.OP_READ;
        if (sess.sessionHasDataToWrite()) {
          interestOps |= SelectionKey.OP_WRITE;
        }
        sess.getSelectionKey().interestOps(interestOps);
      }
      try {
        ioAvailable.select(1);
      } catch (IOException io) {
        LOG.warn("Receieved IOException while" + " selecting for IO", io);
      }

      for (SelectionKey key : ioAvailable.selectedKeys()) {
        GearmanJobServerSession sess = sessionMap.get(key);
        if (sess == null) {
          LOG.warn("Worker does not have " + "session for key " + key);
          continue;
        }
        if (!sess.isInitialized()) {
          continue;
        }
        try {
          GearmanTask sessTask = taskMap.get(sess);
          if (sessTask == null) {
            sessTask =
                new GearmanTask( // NOPMD
                    new GrabJobEventHandler(sess),
                    new GearmanPacketImpl(
                        GearmanPacketMagic.REQ, getGrabJobPacketType(), new byte[0]));
            taskMap.put(sess, sessTask);
            sess.submitTask(sessTask);
            LOG.debug(
                "Worker: "
                    + this
                    + " submitted a "
                    + sessTask.getRequestPacket().getPacketType()
                    + " to session: "
                    + sess);
          }
          sess.driveSessionIO();
          // For the time being we will execute the jobs synchronously
          // in the future, I expect to change this.
          if (!functionList.isEmpty()) {
            GearmanFunction fun = functionList.remove();
            submitFunction(fun);
            statistics();
          }

        } catch (IOException ioe) {
          LOG.warn("Received IOException while driving" + " IO on session " + sess, ioe);
          sess.closeSession();
          continue;
        }
      }
    }

    shutDownWorker(true);
  }
 private GearmanPacketMagic(String kind) {
   this.name = new byte[1 + kind.length()];
   this.name[0] = 0;
   byte[] bytes = ByteUtils.toUTF8Bytes(kind);
   System.arraycopy(bytes, 0, this.name, 1, kind.length());
 }