You are here

class SensorDataController in farmOS 2.x

Handles requests for basic data streams associated with a sensor.

Hierarchy

Expanded class hierarchy of SensorDataController

1 file declares its use of SensorDataController
SensorListenerController.php in modules/asset/sensor/modules/listener/src/Controller/SensorListenerController.php

File

modules/asset/sensor/src/Controller/SensorDataController.php, line 21

Namespace

Drupal\farm_sensor\Controller
View source
class SensorDataController extends ControllerBase {

  /**
   * The basic data stream plugin.
   *
   * @var \Drupal\data_stream\Plugin\DataStream\DataStreamType\Basic
   */
  protected $basicDataStream;

  /**
   * SensorDataController constructor.
   *
   * @param \Drupal\data_stream\DataStreamTypeManager $data_stream_type_manager
   *   The data stream type manager.
   */
  public function __construct(DataStreamTypeManager $data_stream_type_manager) {
    $this->basicDataStream = $data_stream_type_manager
      ->createInstance('basic');
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static($container
      ->get('plugin.manager.data_stream_type'));
  }

  /**
   * Respond to GET or POST requests referencing sensor assets by UUID.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request.
   * @param string $uuid
   *   The sensor asset UUID.
   *
   * @return \Symfony\Component\HttpFoundation\Response
   *   The response.
   */
  public function uuid(Request $request, string $uuid) {

    // Load the sensor asset.
    $sensor_assets = $this
      ->entityTypeManager()
      ->getStorage('asset')
      ->loadByProperties([
      'type' => 'sensor',
      'uuid' => $uuid,
    ]);

    // Bail if UUID is not found.
    if (empty($sensor_assets)) {
      throw new NotFoundHttpException();
    }

    /** @var \Drupal\asset\Entity\AssetInterface $asset */
    $asset = reset($sensor_assets);
    return $this
      ->handleAssetRequest($asset, $request);
  }

  /**
   * Helper function to handle the request once the asset has been loaded.
   *
   * @param \Drupal\asset\Entity\AssetInterface $asset
   *   The asset.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request.
   *
   * @return \Symfony\Component\HttpFoundation\Response
   *   The response.
   */
  protected function handleAssetRequest(AssetInterface $asset, Request $request) {

    /** @var \Drupal\data_stream\Entity\DataStreamInterface[] $data_streams */
    $data_streams = $asset
      ->get('data_stream')
      ->referencedEntities();
    $basic_data_streams = array_filter($data_streams, function ($data_stream) {
      return $data_stream
        ->bundle() === 'basic';
    });

    // Get request method.
    $method = $request
      ->getMethod();
    switch ($method) {
      case Request::METHOD_GET:

        // Bail if the sensor is not public and no private_key is provided.
        if (!$asset
          ->get('public')->value && !$this
          ->requestHasValidPrivateKey($asset, $request)) {
          throw new AccessDeniedHttpException();
        }
        $params = $request->query
          ->all();
        $max_limit = 100000;
        $limit = $max_limit;
        if (isset($params['limit'])) {
          $limit = $params['limit'];

          // Bail if more than the max is requested.
          // Only allow 100k max data points to prevent exhausting PHP's memory,
          // which is a potential DDoS vector.
          if ($limit > $max_limit) {
            throw new UnprocessableHttpEntityException();
          }
        }
        $params['limit'] = $limit;
        $data = $this->basicDataStream
          ->storageGetMultiple($basic_data_streams, $params);
        return JsonResponse::create($data);
      case Request::METHOD_POST:

        // Bail if no private_key is provided.
        if (!$this
          ->requestHasValidPrivateKey($asset, $request)) {
          throw new AccessDeniedHttpException();
        }

        // Load the data.
        $data = Json::decode($request
          ->getContent());

        // Check for new named values.
        $unique_names = $this
          ->getUniqueNamedValues($data);
        $existing_names = array_map(function ($data_stream) {
          return $data_stream
            ->label();
        }, $basic_data_streams);

        // Create new data streams for new named values.
        foreach ($unique_names as $name) {
          if (!in_array($name, $existing_names)) {
            $basic_data_streams[] = $this
              ->createDataStream($asset, $name);
          }
        }

        // Allow each data stream to process the data.
        foreach ($basic_data_streams as $data_stream) {
          $this->basicDataStream
            ->storageSave($data_stream, $data);
        }
        return Response::create('', Response::HTTP_CREATED);
    }

    // Else raise error.
    throw new MethodNotAllowedHttpException($this->basicDataStream
      ->apiAllowedMethods());
  }

  /**
   * Helper function to determine if the request provides a correct private_key.
   *
   * @param \Drupal\asset\Entity\AssetInterface $asset
   *   The asset.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request.
   *
   * @return bool
   *   If the request has access.
   */
  protected function requestHasValidPrivateKey(AssetInterface $asset, Request $request) {
    $private_key = $asset
      ->get('private_key')->value;
    return $private_key == $request
      ->get('private_key', '');
  }

  /**
   * Helper function to extract unique named values from the data payload.
   *
   * @param array $data
   *   The submitted data.
   *
   * @return array
   *   Array of unique names.
   */
  protected function getUniqueNamedValues(array $data) : array {

    // Start an array of names.
    $names = [];

    // If the data is an array of multiple data points, iterate over each and
    // recursively process.
    if (is_array(reset($data))) {
      foreach ($data as $point) {
        $names = array_unique(array_merge($names, $this
          ->getUniqueNamedValues($point)));
      }
      return $names;
    }

    // Iterate over the JSON properties to get each name.
    foreach ($data as $key => $value) {
      if ($key !== 'timestamp') {
        $names[] = $key;
      }
    }
    return array_unique($names);
  }

  /**
   * Helper function to create a new basic data stream associated with a sensor.
   *
   * @param \Drupal\asset\Entity\AssetInterface $asset
   *   The sensor asset.
   * @param string $name
   *   The data stream name.
   *
   * @return \Drupal\Core\Entity\EntityInterface
   *   The new data stream.
   */
  protected function createDataStream(AssetInterface $asset, string $name) {

    // Create new data stream.
    $new_data_stream = $this
      ->entityTypeManager()
      ->getStorage('data_stream')
      ->create([
      'type' => 'basic',
      'name' => $name,
    ]);
    $new_data_stream
      ->save();

    // Assign to the host sensor asset.

    /** @var \Drupal\Core\Field\EntityReferenceFieldItemList $data_stream_field */
    $data_stream_field = $asset
      ->get('data_stream');
    $data_stream_field
      ->appendItem($new_data_stream);
    $asset
      ->save();
    return $new_data_stream;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ControllerBase::$configFactory protected property The configuration factory.
ControllerBase::$currentUser protected property The current user service. 1
ControllerBase::$entityFormBuilder protected property The entity form builder.
ControllerBase::$entityTypeManager protected property The entity type manager.
ControllerBase::$formBuilder protected property The form builder. 2
ControllerBase::$keyValue protected property The key-value storage. 1
ControllerBase::$languageManager protected property The language manager. 1
ControllerBase::$moduleHandler protected property The module handler. 2
ControllerBase::$stateService protected property The state service.
ControllerBase::cache protected function Returns the requested cache bin.
ControllerBase::config protected function Retrieves a configuration object.
ControllerBase::container private function Returns the service container.
ControllerBase::currentUser protected function Returns the current user. 1
ControllerBase::entityFormBuilder protected function Retrieves the entity form builder.
ControllerBase::entityTypeManager protected function Retrieves the entity type manager.
ControllerBase::formBuilder protected function Returns the form builder service. 2
ControllerBase::keyValue protected function Returns a key/value storage collection. 1
ControllerBase::languageManager protected function Returns the language manager service. 1
ControllerBase::moduleHandler protected function Returns the module handler. 2
ControllerBase::redirect protected function Returns a redirect response object for the specified route.
ControllerBase::state protected function Returns the state storage service.
LoggerChannelTrait::$loggerFactory protected property The logger channel factory service.
LoggerChannelTrait::getLogger protected function Gets the logger for a specific channel.
LoggerChannelTrait::setLoggerFactory public function Injects the logger channel factory.
MessengerTrait::$messenger protected property The messenger. 27
MessengerTrait::messenger public function Gets the messenger. 27
MessengerTrait::setMessenger public function Sets the messenger.
RedirectDestinationTrait::$redirectDestination protected property The redirect destination service. 1
RedirectDestinationTrait::getDestinationArray protected function Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url.
RedirectDestinationTrait::getRedirectDestination protected function Returns the redirect destination service.
RedirectDestinationTrait::setRedirectDestination public function Sets the redirect destination service.
SensorDataController::$basicDataStream protected property The basic data stream plugin.
SensorDataController::create public static function Instantiates a new instance of this class. Overrides ControllerBase::create
SensorDataController::createDataStream protected function Helper function to create a new basic data stream associated with a sensor.
SensorDataController::getUniqueNamedValues protected function Helper function to extract unique named values from the data payload.
SensorDataController::handleAssetRequest protected function Helper function to handle the request once the asset has been loaded.
SensorDataController::requestHasValidPrivateKey protected function Helper function to determine if the request provides a correct private_key.
SensorDataController::uuid public function Respond to GET or POST requests referencing sensor assets by UUID.
SensorDataController::__construct public function SensorDataController constructor.
StringTranslationTrait::$stringTranslation protected property The string translation service. 4
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.