/**
  * Adds an object to the persistent data store.
  *
  * @param \SmashPig\Core\DataStores\KeyedOpaqueStorableObject $obj
  *
  * @throws \SmashPig\Core\DataStores\DataStoreException if the message could not be stored.
  * @return null
  */
 public function addObject(KeyedOpaqueStorableObject $obj)
 {
     $keys = $obj->getObjectKeys();
     // FIXME magic string is magic
     if (!array_key_exists('correlationId', $keys)) {
         throw new DataStoreException("Required property correlationId was not exposed.");
     }
     $corrId = $keys['correlationId'];
     if (!isset($this->messages[$corrId])) {
         $this->messages[$corrId] = array();
     }
     array_push($this->messages[$corrId], $obj);
 }
 /**
  * Instantiates and runs a job defined by a queue message. Depends on
  * the base consumer's damaged message store functionality to either
  * divert messages or stop execution on bad message or job failure.
  * @param array $jobMessage
  * @throws \SmashPig\Core\DataStores\DataSerializationException
  */
 function processMessage($jobMessage)
 {
     if (!isset($jobMessage['php-message-class'])) {
         throw new RuntimeException('Job message missing required key \'php-message-class\'');
     }
     // TODO: encapsulate the reconstitution step elsewhere.
     // FIXME It seems bad that these objects indiscriminately store
     // things as properties. The message is mingled with stuff like
     // php-message-class. Could collide.
     $className = $jobMessage['php-message-class'];
     Logger::info("Hydrating a message with class {$className}");
     $jsonMessage = json_encode($jobMessage);
     Logger::debug("Job payload: {$jsonMessage}");
     $jobObj = KeyedOpaqueStorableObject::fromJsonProxy($className, $jsonMessage);
     if ($jobObj instanceof Runnable) {
         Logger::info('Running job');
         if (!$jobObj->execute()) {
             throw new RuntimeException('Job tells us that it did not successfully execute. ' . 'Sending to damaged message store.');
         }
     } else {
         // We don't know how to run this job type.
         throw new RuntimeException(get_class($jobObj) . ' is not an instance of Runnable. ' . 'Could not execute and sending to damaged message store.');
     }
     $this->successCount += 1;
 }
 /**
  * Adds an object to the persistent data store.
  *
  * @param KeyedOpaqueStorableObject $obj
  *
  * @throws DataStoreException if the message could not be stored.
  * @return null
  */
 public function addObject(KeyedOpaqueStorableObject $obj)
 {
     $keys = $obj->getObjectKeys();
     $objFileName = $this->constructFileName(Context::get()->getContextId(), $keys, $this->insertCount++);
     $objFsPath = $this->basePath . '/objects/' . $objFileName;
     /* --- Create the root object file --- */
     if (file_exists($objFsPath) || ($fptr = fopen($objFsPath, 'xb')) === false) {
         throw new DataStoreException("Could not add object to store! Fully qualified key '{$objFileName}' already exists!");
     }
     fwrite($fptr, get_class($obj));
     fwrite($fptr, "\n");
     fwrite($fptr, $obj->toJson());
     fclose($fptr);
     /* === Create the helper linking files === */
     /* --- Class file first --- */
     $this->addKeyedLinkingFile('class', get_class($obj), $objFileName, $objFsPath);
     /* --- Everything else --- */
     foreach ($keys as $key => $value) {
         $this->addKeyedLinkingFile($key, $value, $objFileName, $objFsPath);
     }
 }
 public function testConsume()
 {
     foreach (self::$messages as $msg) {
         $this->capture($msg['payload']);
         $jobQueue = $this->config->object('data-store/jobs-paypal');
         $jobMessage = $jobQueue->pop();
         $job = KeyedOpaqueStorableObject::fromJsonProxy($jobMessage['php-message-class'], json_encode($jobMessage));
         $job->execute();
         $queue = $this->config->object('data-store/' . $msg['type']);
         $queue->createTable($msg['type']);
         $message = $queue->pop();
         if ($job->is_reject()) {
             $this->assertEmpty($message);
         } else {
             $this->assertNotEmpty($message);
             if (isset($message['contribution_tracking_id'])) {
                 $this->assertEquals($message['contribution_tracking_id'], $message['order_id']);
             }
         }
     }
 }
 public function testRecordCapture()
 {
     $verifiedQueue = BaseQueueConsumer::getQueue('verified');
     $verifiedQueue->createTable('verified');
     $capture = KeyedOpaqueStorableObject::fromJsonProxy('SmashPig\\PaymentProviders\\Adyen\\ExpatriatedMessages\\Capture', file_get_contents(__DIR__ . '/../Data/capture.json'));
     $job = RecordCaptureJob::factory($capture);
     $this->assertTrue($job->execute());
     $donorData = $this->pendingDatabase->fetchMessageByGatewayOrderId('adyen', $capture->merchantReference);
     $this->assertNull($donorData, 'RecordCaptureJob left donor data on pending queue');
     $verifiedMessage = $verifiedQueue->pop();
     $this->assertNotNull($verifiedMessage, 'RecordCaptureJob did not send verified message');
     // can we use arraySubset yet?
     $sameKeys = array_intersect(array_keys($verifiedMessage), array_keys($this->pendingMessage));
     foreach ($sameKeys as $key) {
         if ($key === 'gateway_txn_id') {
             $this->assertEquals($capture->originalReference, $verifiedMessage[$key], 'RecordCaptureJob should have set gateway_txn_id');
         } else {
             $this->assertEquals($this->pendingMessage[$key], $verifiedMessage[$key], "Value of key {$key} mutated");
         }
     }
 }
 public function __construct()
 {
     parent::__construct();
     $this->{'php-message-class'} = get_called_class();
 }
 /**
  * @expectedException \SmashPig\Core\RetryableException
  */
 public function testRequeueMessage()
 {
     $auth = KeyedOpaqueStorableObject::fromJsonProxy('SmashPig\\PaymentProviders\\Adyen\\ExpatriatedMessages\\Authorisation', file_get_contents(__DIR__ . '/../Data/auth.json'));
     $job = ProcessCaptureRequestJob::factory($auth);
     $job->execute();
 }
 /**
  * Operate the datastore as a queue. Will retrieve messages, one at a time,
  * from the backing store ensuring that no other running process may obtain
  * the same message.
  *
  * Any message obtained via this function must be either acknowledged (and
  * thus removed from the backing store) or ignored (whereby it is replaced
  * into the backing store). Only once one of these operations is completed
  * may another message be obtained from the backing store.
  *
  * If a message has not yet been completely when this function gets called,
  * it will throw a DataStoreTransactionException exception.
  *
  * If there were no messages fitting the filter, null will be returned.
  *
  * @param null|string    $type      The class of message to retrieve (if null retrieves all)
  * @param null|string    $id        The correlation ID of the message (if null retrieves all)
  * @param string[]       $customSelectors Array of custom STOMP selectors (e.g 'gateway=adyen')
  * @param bool           $deserialize If true will return an object. Otherwise will return an array
  *                                    with keys 'headers' and 'body'.
  *
  * @throws DataStoreTransactionException
  * @throws DataSerializationException
  * @throws DataStoreException
  * @return KeyedOpaqueStorableObject|array|null
  */
 public function queueGetObject($type = null, $id = null, $customSelectors = array(), $deserialize = true)
 {
     $msgObj = $this->queueGetObjectRaw($type, $id, $customSelectors);
     if ($msgObj && $deserialize) {
         if (!array_key_exists('php-message-class', $msgObj->headers)) {
             Logger::warning("Message was serialized without key php-message-class. Cannot re-instantiate.", $msgObj);
             $this->queueIgnoreObject();
             // I'm not a huge fan of this recursion; but I don't know what else to do
             return $this->queueGetObject($type, $id, $customSelectors);
         }
         $className = $msgObj->headers['php-message-class'];
         if (!class_exists($className)) {
             Logger::warning("DataStore cannot instantiate object from message. No such class '{$className}'.", $msgObj);
             throw new DataStoreException("Cannot instantiate class '{$className}'; no such class exists.");
         }
         try {
             $classObj = KeyedOpaqueStorableObject::fromJsonProxy($className, $msgObj->body);
             $classObj->correlationId = $msgObj->headers['correlation-id'];
         } catch (DataSerializationException $ex) {
             Logger::warning("DataStore cannot instantiate object from STOMP message.", $msgObj, $ex);
             throw $ex;
         }
         return $classObj;
     } elseif ($msgObj && !$deserialize) {
         return array('headers' => $msgObj->headers, 'body' => $msgObj->body);
     } else {
         return null;
     }
 }
 /**
  * When two authorizations come in with the same merchant reference, we
  * should cancel the second one and leave the donor details in pending.
  */
 public function testDuplicateAuthorisation()
 {
     $api = $this->config->object('payment-provider/adyen/api', true);
     $auth1 = KeyedOpaqueStorableObject::fromJsonProxy('SmashPig\\PaymentProviders\\Adyen\\ExpatriatedMessages\\Authorisation', file_get_contents(__DIR__ . '/../Data/auth.json'));
     $job1 = ProcessCaptureRequestJob::factory($auth1);
     $job1->execute();
     $this->assertEquals(1, count($api->captured), 'Set up failed');
     $auth2 = KeyedOpaqueStorableObject::fromJsonProxy('SmashPig\\PaymentProviders\\Adyen\\ExpatriatedMessages\\Authorisation', file_get_contents(__DIR__ . '/../Data/auth.json'));
     $auth2->pspReference = mt_rand(1000000000, 10000000000);
     $job2 = ProcessCaptureRequestJob::factory($auth2);
     $this->assertTrue($job2->execute(), 'Duplicate auths should not clutter damage queue');
     $this->assertEquals(1, count($api->captured), 'Captured a duplicate!');
     $this->assertEquals($auth2->pspReference, $api->cancelled[0], 'Did not cancel the right authorization');
     $this->assertNotNull($this->pendingDatabase->fetchMessageByGatewayOrderId('adyen', $auth1->merchantReference), 'Capture job should leave donor details in database');
 }