All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.powertac.tariffmarket.TariffMarketService Maven / Gradle / Ivy

There is a newer version: 1.9.0
Show newest version
/*
 * Copyright 2011-2013 the original author or authors.
 *
 * 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.
 */
package org.powertac.tariffmarket;

import static org.powertac.util.ListTools.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Instant;
import org.powertac.common.Competition;
import org.powertac.common.CustomerInfo;
import org.powertac.common.Broker;
import org.powertac.common.RandomSeed;
import org.powertac.common.Rate;
import org.powertac.common.RegulationRate;
import org.powertac.common.Tariff;
import org.powertac.common.TariffMessage;
import org.powertac.common.TariffSpecification;
import org.powertac.common.TariffSubscription;
import org.powertac.common.TariffTransaction;
import org.powertac.common.TimeService;
import org.powertac.common.config.ConfigurableValue;
import org.powertac.common.enumerations.PowerType;
import org.powertac.common.interfaces.Accounting;
import org.powertac.common.interfaces.CapacityControl;
import org.powertac.common.interfaces.BrokerProxy;
import org.powertac.common.interfaces.InitializationService;
import org.powertac.common.interfaces.NewTariffListener;
import org.powertac.common.interfaces.ServerConfiguration;
import org.powertac.common.interfaces.TariffMarket;
import org.powertac.common.interfaces.TimeslotPhaseProcessor;
import org.powertac.common.msg.BalancingOrder;
import org.powertac.common.msg.EconomicControlEvent;
import org.powertac.common.msg.TariffExpire;
import org.powertac.common.msg.TariffRevoke;
import org.powertac.common.msg.TariffStatus;
import org.powertac.common.msg.TariffUpdate;
import org.powertac.common.msg.VariableRateUpdate;
import org.powertac.common.repo.BrokerRepo;
import org.powertac.common.repo.RandomSeedRepo;
import org.powertac.common.repo.TariffRepo;
import org.powertac.common.repo.TariffSubscriptionRepo;
import org.powertac.common.repo.TimeslotRepo;
import org.powertac.common.spring.SpringApplicationContext;
import org.powertac.util.Predicate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * Implements the Tariff Market abstraction. Incoming tariff-related
 * messages from brokers are received and processed, tariffs are published
 * periodically, and subscriptions are processed on behalf of customers.
 * @author John Collins
 */
@Service
public class TariffMarketService
  extends TimeslotPhaseProcessor 
  implements TariffMarket, InitializationService
{
  static private Logger log =
          LogManager.getLogger(TariffMarketService.class.getSimpleName());

  @Autowired
  private TimeService timeService;
  
  @Autowired
  private Accounting accountingService;
  
  @Autowired
  private CapacityControl capacityControlService;
  
  @Autowired
  private BrokerProxy brokerProxyService;
  
  @Autowired
  private BrokerRepo brokerRepo;
  
  @Autowired
  private TimeslotRepo timeslotRepo;
  
  @Autowired
  private TariffRepo tariffRepo;
  
  @Autowired
  private TariffSubscriptionRepo tariffSubscriptionRepo;
  
  @Autowired
  private ServerConfiguration serverProps;

  @Autowired
  private RandomSeedRepo randomSeedService;
  
  // list of tariffs that have been revoked but not processed
  private ArrayList pendingRevokedTariffs = new ArrayList<> ();
  // list of revoked but not yet deleted tariffs
  private List revokedTariffs = null;
  private Instant lastRevokeProcess = new Instant(0l);

  // configure tariff publication fees
  @ConfigurableValue(valueType = "Double",
      description = "low end of tariff publication fee range")
  private double minPublicationFee = -100.0;

  @ConfigurableValue(valueType = "Double",
      description = "high end of tariff publication fee range")
  private double maxPublicationFee = -500.0;

  @ConfigurableValue(valueType = "Double",
      publish = true,
      description = "set publication fee directly to override random selection")
  private Double publicationFee = null;

  // configure tariff revocation fees
  @ConfigurableValue(valueType = "Double",
      description = "low end of tariff revocation fee range")
  private double minRevocationFee = -100.0;

  @ConfigurableValue(valueType = "Double",
      description = "high end of tariff revocation fee range")
  private double maxRevocationFee = -500.0;

  @ConfigurableValue(valueType = "Double",
      publish = true,
      description = "Set revocation fee directly to override random selection")
  private Double revocationFee = null;

  // these properties are constrained, so we provide explicit setters for them
  private int publicationInterval = 6;
  private int publicationOffset = 0;
  private boolean subsequentPublication;

  // list of pending subscription events.
  private List pendingSubscriptionEvents =
      new ArrayList<>();

  // list of pending variable-rate updates.
  private List pendingVrus = new ArrayList<>();

  // set of already-disabled brokers
  private HashSet disabledBrokers = new HashSet<>();

  private Set registrations = new LinkedHashSet<>();

  /**
   * Default constructor
   */
  public TariffMarketService ()
  {
    super();
  }

  /**
   * Reads configuration parameters, registers for timeslot phase activation.
   */
  @Override
  public String initialize (Competition competition, List completedInits)
  {
    int index = completedInits.indexOf("AccountingService");
    if (index == -1) {
      return null;
    }

    for (Class messageType: Arrays.asList(TariffSpecification.class,
                                             TariffExpire.class,
                                             TariffRevoke.class,
                                             VariableRateUpdate.class,
                                             EconomicControlEvent.class,
                                             BalancingOrder.class)) {
      brokerProxyService.registerBrokerMessageListener(this, messageType);
    }
    subsequentPublication = false;
    registrations.clear();
    publicationFee = null;
    revocationFee = null;
    
    super.init();
    
    pendingSubscriptionEvents.clear();
    pendingRevokedTariffs.clear();
    pendingVrus.clear();
    disabledBrokers.clear();
    revokedTariffs = null;
    lastRevokeProcess = new Instant(0l);

    serverProps.configureMe(this);

    // Register the NewTariffListeners
    List listeners =
        SpringApplicationContext.listBeansOfType(NewTariffListener.class);
    for (NewTariffListener listener : listeners) {
      registerNewTariffListener(listener);
    }

    // compute the fees
    RandomSeed random =
        randomSeedService.getRandomSeed("TariffMarket",
                                        0l, "fees");
    if (publicationFee == null) {
      // interest will be non-null in case it was overridden in the config
      publicationFee = (minPublicationFee +
                             (random.nextDouble() *
                                 (maxPublicationFee - minPublicationFee)));
      log.info("set publication fee: " + publicationFee);
    }
    if (revocationFee == null) {
      // interest will be non-null in case it was overridden in the config
      revocationFee = (minRevocationFee +
                             (random.nextDouble() *
                                 (maxRevocationFee - minRevocationFee)));
      log.info("set revocation fee: " + revocationFee);
    }
    serverProps.publishConfiguration(this);
    return "TariffMarket";
  }

  // ------------ Data access and configuration support ---------------

  // publication fee
  public double getMinPublicationFee ()
  {
    return minPublicationFee;
  }

  public double getMaxPublicationFee ()
  {
    return maxPublicationFee;
  }

  public Double getPublicationFee ()
  {
    return publicationFee;
  }

  // revocation fee
  public double getMinRevocationFee ()
  {
    return minRevocationFee;
  }

  public double getMaxRevocationFee ()
  {
    return maxRevocationFee;
  }

  public Double getRevocationFee ()
  {
    return revocationFee;
  }

  public int getPublicationInterval ()
  {
    return publicationInterval;
  }

  @ConfigurableValue(valueType = "Integer",
      description = "Number of timeslots between tariff publication events. " +
                    "Must be at most 24.")
  public void setPublicationInterval (int interval)
  {
    if (interval > 24) {
      log.error("tariff publication interval " + interval + " > 24 hr");
      interval = 24;
    }
    publicationInterval = interval;
  }

  public int getPublicationOffset ()
  {
    return publicationOffset;
  }

  @ConfigurableValue(valueType = "Integer",
      description = "Number of timeslots from the first timeslot to delay " +
          "the first publication event. It does not work well " +
          "to make this zero, because brokers do not have an opportunity " +
          "to post tariffs in timeslot 0.")
  public void setPublicationOffset (int offset)
  {
    if (offset >= publicationInterval) {
      log.error("tariff publication offset " + publicationOffset
                + " >= publication interval " + offset);
    }
    else {
      publicationOffset = offset;
    }
  }

  // Test support
  List getRegistrations ()
  {
    return new ArrayList<>(registrations);
  }

  // ----------------- Broker message API --------------------
  // Receive incoming broker messages. We do this
  // synchronously with the incoming message traffic, rather than on
  // the timeslot phase signal, to minimize latency for broker feedback.

  /**
   * Processes a newly-published tariff.
   */
  public void handleMessage (TariffSpecification spec)
  {
    if (!(null == tariffRepo.findSpecificationById(spec.getId()) ||
            tariffRepo.isRemoved(spec.getId()))) {
      log.warn("duplicate tariff spec from " + spec.getBroker().getUsername() +
               ", id = " + spec.getId());
      send(new TariffStatus(spec.getBroker(), spec.getId(), spec.getId(),
                            TariffStatus.Status.invalidTariff)
          .withMessage("duplicate tariff spec " + spec.getId()));
      return;
    }
    if (null == spec.getRates()) {
      log.warn("no rates given for spec " + spec.getId());
      send(new TariffStatus(spec.getBroker(), spec.getId(), spec.getId(),
                            TariffStatus.Status.invalidTariff)
          .withMessage("missing Rates"));
      return;
    }
    else if (!spec.isValid()) {
      log.warn("invalid spec " + spec.getId());
      send(new TariffStatus(spec.getBroker(), spec.getId(), spec.getId(),
                            TariffStatus.Status.invalidTariff)
          .withMessage("spec fails validity test"));
      return;
    }
    else if (null != spec.getSupersedes()) {
      for (Long supersede : spec.getSupersedes()) {
        TariffSpecification other = tariffRepo.findSpecificationById(supersede);
        if (null == other) {
          log.warn("attempt to supersede non-existent tariff " + supersede);
          send(new TariffStatus(spec.getBroker(), spec.getId(), spec.getId(),
                                TariffStatus.Status.invalidTariff)
              .withMessage("non-existent supersede " + supersede));
          return;
        }
        else if (spec.getBroker() != other.getBroker()) {
          log.warn("attempt by " + spec.getBroker().getUsername()
                   + " to supersede tariff of "
                   + other.getBroker().getUsername());
          send(new TariffStatus(spec.getBroker(), spec.getId(), spec.getId(),
                                TariffStatus.Status.invalidTariff)
              .withMessage("invalid supersede " + supersede));
          return;
        }
      }
    }
    //validate rates
    for (Rate rate : spec.getRates()) {
      if (rate.getDailyBegin() >= 24 || rate.getDailyEnd() >= 24 ||
              rate.getWeeklyBegin() == 0 || rate.getWeeklyBegin() > 7 ||
              rate.getWeeklyEnd() == 0 || rate.getWeeklyEnd() > 7) {
        log.warn("invalid rate for spec " + spec.getId());
        send(new TariffStatus(spec.getBroker(), spec.getId(), spec.getId(),
                              TariffStatus.Status.invalidTariff)
            .withMessage("spec has invalid Rate"));
        return;
      }
    }
    tariffRepo.addSpecification(spec);
    Tariff tariff = new Tariff(spec);
    if (!tariff.init()) {
      log.warn("incomplete coverage in multi-rate tariff " + spec.getId());
      tariffRepo.removeTariff(tariff);
      send(new TariffStatus(spec.getBroker(), spec.getId(), spec.getId(),
                            TariffStatus.Status.invalidTariff)
          .withMessage("incomplete coverage in multi-rate tariff"));
      return;
    }
    for (RegulationRate regRate : spec.getRegulationRates()) {
      if (regRate.getResponse() == RegulationRate.ResponseTime.SECONDS) {
        log.warn("discarding fast-response RegulationRate tariff {}",
                 spec.getId());
        continue;
      }
      if (spec.getPowerType().isInterruptible()
          || spec.getPowerType().isStorage()) {
        // Automatic composition of balancing orders -- see Issue #946
        if (regRate.getUpRegulationPayment() != 0.0) {
          BalancingOrder bo =
              new BalancingOrder(spec.getBroker(), spec,
                                 (spec.getPowerType().isStorage()? 2.0: 1.0),
                                 regRate.getUpRegulationPayment());
          tariffRepo.addBalancingOrder(bo);
        }
        if (regRate.getDownRegulationPayment() != 0.0) {
          BalancingOrder bo =
              new BalancingOrder(spec.getBroker(), spec, -1.0,
                                 regRate.getDownRegulationPayment());
          tariffRepo.addBalancingOrder(bo);
        }
      }
    }
    log.info("new tariff " + spec.getId());
    accountingService.addTariffTransaction(TariffTransaction.Type.PUBLISH,
                                           tariff, null, 0, 0.0, publicationFee);
    send(new TariffStatus(spec.getBroker(),
                          spec.getId(),
                          spec.getId(),
                          TariffStatus.Status.success));
  }

  /**
   * Handles changes in tariff expiration date.
   */
  public void handleMessage (TariffExpire update)
  {
    ValidationResult result = validateUpdate(update);
    if (result.tariff == null)
      send(result.message);
    else {
      Instant newExp = update.getNewExpiration();
      if (newExp != null && newExp.isBefore(timeService.getCurrentTime())) {
        // new expiration date in the past
        log.warn("attempt to set expiration for tariff " +
                 result.tariff.getId() + " in the past:" +
                 newExp.toString());
        send(new TariffStatus(update.getBroker(),
                              update.getTariffId(),
                              update.getId(),
                              TariffStatus.Status.invalidUpdate)
             .withMessage("attempt to set expiration in the past"));
      }
      else {
        // update expiration date
        result.tariff.setExpiration(newExp);
        log.info("Tariff " + update.getTariffId() + 
                 "now expires at " + new DateTime(result.tariff.getExpiration(), DateTimeZone.UTC).toString());
        success(update);
      }
    }
  }

  /**
   * Handles tariff revocation.
   */
  public void handleMessage (TariffRevoke update)
  {
    // basic validation
    ValidationResult result = validateUpdate(update);
    if (result.tariff == null) {
      send(result.message);
      return;
    }
    addPendingRevoke(result.tariff);
    success(update);
  }

  /**
   * Applies a new HourlyCharge to an existing Tariff with a variable Rate.
   */
  public void handleMessage (VariableRateUpdate update)
  {
    ValidationResult result = validateUpdate(update);
    if (result.tariff == null) {
      send(result.message);
      return;
    }
    Rate rate = tariffRepo.findRateById(update.getRateId());
    if (rate == null) {
      send(new TariffStatus(update.getBroker(),
                            update.getTariffId(),
                            update.getId(),
                            TariffStatus.Status.invalidUpdate)
           .withMessage("Non-existent rate in VRU"));
      return;
    }
    if (!result.tariff.getTariffSpecification().getRates().contains(rate)) {
      send(new TariffStatus(update.getBroker(),
                            update.getTariffId(),
                            update.getId(),
                            TariffStatus.Status.invalidUpdate)
           .withMessage("Rate not associated with tariff in VRU"));
      return;
    }
    if (!update.isValid(rate)) {
      send(new TariffStatus(update.getBroker(),
                            update.getTariffId(),
                            update.getId(),
                            TariffStatus.Status.invalidUpdate)
           .withMessage("Invalid charge in VRU"));
      return;
    }
    addVru(update);
  }
  
  /**
   * Processes an incoming ControlEvent from a broker
   */
  public synchronized void handleMessage (EconomicControlEvent msg)
  {
    ValidationResult result = validateUpdate(msg);
    if (result.tariff == null) {
      send(result.message);
      return;
    }
    int currentTimeslot = timeslotRepo.currentTimeslot().getSerialNumber();
    if (currentTimeslot > msg.getTimeslotIndex()) {
      // this is in the past
      log.warn("Curtailment requested in ts " + currentTimeslot +
               " for past timeslot " + msg.getTimeslotIndex());
      // send error?
      send(new TariffStatus(msg.getBroker(),
                            msg.getTariffId(),
                            msg.getId(),
                            TariffStatus.Status.invalidUpdate)
          .withMessage("control: specified timeslot in the past"));
      return;
    }
    capacityControlService.postEconomicControl(msg);
  }

  /**
   * Processes an incoming BalancingOrder by storing it in the tariffRepo.
   * Balancing orders can be used for interruptible tariffs, but not in the
   * presence of RegulationRates. For tariffs with RegulationRates, the
   * BalancingOrders are automatically constructed from the RRs. In other words,
   * BalancingOrders can be used to permit up-regulation through curtailment, but 
   * not if the customer expects to be paid for the balancing events.
   */
  public synchronized void handleMessage (BalancingOrder msg)
  {
    ValidationResult result = validateUpdate(msg);
    if (result.tariff == null) {
      send(result.message);
      return;
    }
    else {
      // not allowed if tariff has RegulationRates
      if (result.tariff.hasRegulationRate()) {
        result.message.withMessage("Cannot use BO with RegulationRate")
        .setStatus(TariffStatus.Status.unsupported);
        send(result.message);
        return;
      }
      // not allowed if no rates allow curtailment
      boolean curtailmentAllowed = false;
      for (Rate rate: result.tariff.getTariffSpecification().getRates()) {
        if (rate.getMaxCurtailment() > 0.0) {
          curtailmentAllowed = true;
          break;
        }
      }
      if (!curtailmentAllowed) {
        result.message.withMessage("Cannot use BO without curtailment")
        .setStatus(TariffStatus.Status.unsupported);
        send(result.message);
        return;
      }
      // range check for exercise ratio
      if (msg.getExerciseRatio() <= 0.0 || msg.getExerciseRatio() > 1.0) {
        result.message.withMessage("Exercise ratio "
            + msg.getExerciseRatio() + " out of range")
            .setStatus(TariffStatus.Status.unsupported);
        send(result.message);
        return;
      }
    }
    tariffRepo.addBalancingOrder(msg);
  }

  // ----------------------- Customer API --------------------------

  private synchronized void addPendingRevoke (Tariff tariff)
  {
    pendingRevokedTariffs.add(tariff);
  }
  
  private synchronized List getPendingRevokes ()
  {
    Instant now = timeService.getCurrentTime();
    if (now.isAfter(lastRevokeProcess)) {
      lastRevokeProcess = now;
      List result = new ArrayList<>(pendingRevokedTariffs);
      pendingRevokedTariffs.clear();
      return result;
    }
    return null; // only get non-null result once/timeslot
  }
  
  /**
   * Runs through the list of pending tariff revocations, marking the tariffs
   * and their subscriptions.
   */
  @Override
  public void processRevokedTariffs ()
  {
    // nothing happens -- this is deprecated.
  }

  private void revokeTariffsForDisabledBrokers ()
  {
    for (Broker broker : brokerRepo.findDisabledBrokers()) {
      if (!disabledBrokers.contains(broker)) {
        // this is a new one
        disabledBrokers.add(broker);
        for (Tariff tariff : tariffRepo.findTariffsByBroker(broker)) {
          if (Tariff.State.KILLED != tariff.getState()) {
            log.info("Revoking tariff " + tariff.getId()
                     + " from disabled broker " + broker.getUsername());
            addPendingRevoke(tariff);
          }
        }
      }
    }
  }

  private void updateRevokedTariffs ()
  {
    List pending = getPendingRevokes();
    if (pending == null)
      return;
    
    revokedTariffs = pending;
    for (Tariff tariff : pending) {
      tariff.setState(Tariff.State.KILLED);
      log.info("Revoke tariff " + tariff.getId());
      // Notify all brokers - issue #719
      TariffRevoke msg = new TariffRevoke(tariff.getBroker(),
                                          tariff.getTariffSpecification());
      brokerProxyService.broadcastMessage(msg);

      // The actual revocation processing is delegated to the Customer,
      // who is obligated to call getRevokedSubscriptions periodically.

      // If there are active subscriptions, then we have to charge a fee.
      List activeSubscriptions =
          filter(tariffSubscriptionRepo.findSubscriptionsForTariff(tariff),
                 new Predicate () {
            @Override
            public boolean apply (TariffSubscription sub) {
              return (sub.getCustomersCommitted() > 0);
            }
          });
      if (activeSubscriptions.size() > 0) {
        log.info("Revoked tariff " + tariff.getId() +
                 " has " + activeSubscriptions.size() + 
                 " active subscriptions");
        accountingService.addTariffTransaction(TariffTransaction.Type.REVOKE,
                                               tariff, null, 0, 0.0,
                                               revocationFee);
      }
    }
  }
  
  // Removes revoked tariffs and their subscriptions from their respective
  // repos.
  void removeRevokedTariffs ()
  {
    if (null == revokedTariffs)
      return;

    for (Tariff tariff : revokedTariffs) {
      // remove all subscriptions
      tariffSubscriptionRepo.removeSubscriptionsForTariff(tariff);
      
      // then remove the tariff and the tariffSpec
      tariffRepo.removeTariff(tariff);
    }
    revokedTariffs = null;
  }

  @Override
  public void registerNewTariffListener (NewTariffListener listener)
  {
    registrations.add(listener);
  }

  // Process queued messages, then
  // handle distribution of new tariffs to customers
  @Override
  public void activate (Instant time, int phase)
  {
    log.info("Activate");
    processPendingVrus();
    long msec = timeService.getCurrentTime().getMillis();
    if (!subsequentPublication ||
        (msec / TimeService.HOUR) % publicationInterval == publicationOffset) {
      // time to publish or never published
      revokeTariffsForDisabledBrokers();
      updateRevokedTariffs();
      publishTariffs();
      //removeRevokedTariffs();
      processPendingSubscriptions();
      subsequentPublication = true;
    }
  }

  /**
   * Publishes pending tariffs to customers and brokers
   */
  private void publishTariffs ()
  {
    List publishedTariffs = tariffRepo.findTariffsByState(Tariff.State.PENDING);
    log.info("publishing " + publishedTariffs.size() + " new tariffs");
    for (Tariff tariff : publishedTariffs) {
      tariff.setState(Tariff.State.OFFERED);
    }

    List publishedTariffSpecs = new ArrayList<>();
    for (Tariff tariff : publishedTariffs) {
      TariffSpecification spec = tariff.getTariffSpecification();
      publishedTariffSpecs.add(spec);
      log.info("publishing spec " + spec.getId() + " broker: " + spec.getBroker().getUsername() + ", exp: " + spec.getExpiration());
    }

    for (NewTariffListener listener : registrations) {
      listener.publishNewTariffs(publishedTariffs);
    }
    brokerProxyService.broadcastMessages(publishedTariffSpecs);
  }

  @Override
  public List getActiveTariffList(PowerType type)
  {
    return tariffRepo.findActiveTariffs(type);
  }

  /**
   * Returns the default tariff
   */
  @Override
  public Tariff getDefaultTariff (PowerType type)
  {
    return tariffRepo.getDefaultTariff(type);
  }

  @Override
  public boolean setDefaultTariff (TariffSpecification newSpec)
  {
    tariffRepo.setDefaultTariff(newSpec);
    return true;
  }
  
  // ----------- Subscribe/unsubscribe processing --------------

  /**
   * If customerCount is positive, subscribes a block of Customers
   * from a single Customer model to the specified Tariff, as long
   * as the Tariff is not expired or revoked. If customerCount is negative,
   * unsubscribes a block of customers from the specified tariff.
   * Processing is deferred unless the customer has no subscriptions,
   * which should only be true at the start of a boot or sim session.
   * 

* Note that you cannot unsubscribe directly from a Tariff -- you have to do * that from the TariffSubscription that represents the Tariff you want * to unsubscribe from.

*/ @Override public void subscribeToTariff (Tariff tariff, CustomerInfo customer, int customerCount) { if (customerCount < 0 || !(tariff.isExpired() || tariff.isRevoked())) { postPendingSubscriptionEvent(tariff, customer, customerCount); List existingSubscriptions = tariffSubscriptionRepo.findSubscriptionsForCustomer(customer); if (0 == existingSubscriptions.size()) { // immediate processing of initial subscriptions processPendingSubscriptions(); } } else log.warn("Attempt to subscribe to " + (tariff.isRevoked() ? "revoked" : "expired") + " tariff"); } /** * Adds a pending subscribe/unsubscribe for later processing */ private synchronized void postPendingSubscriptionEvent (Tariff tariff, CustomerInfo customer, int customerCount) { PendingSubscription event = new PendingSubscription(tariff, customer, customerCount); pendingSubscriptionEvents.add(event); } /** * Handles pending subscription/unsubscription events */ private synchronized void processPendingSubscriptions() { for (PendingSubscription pending : pendingSubscriptionEvents) { TariffSubscription sub = tariffSubscriptionRepo.getSubscription(pending.customer, pending.tariff); if (pending.count > 0) sub.subscribe(pending.count); else if (pending.count < 0) sub.deferredUnsubscribe(-pending.count); } pendingSubscriptionEvents.clear(); } /** * Handles pending vru messages */ private void processPendingVrus () { for (VariableRateUpdate vru: getVruList()) { Tariff tariff = tariffRepo.findTariffById(vru.getTariffId()); if (tariff.addHourlyCharge(vru.getHourlyCharge(), vru.getRateId())) { success(vru); } else { // failed to add hourly charge send(new TariffStatus(vru.getBroker(), vru.getTariffId(), vru.getId(), TariffStatus.Status.invalidUpdate) .withMessage("update: could not add hourly charge")); } } } // adds a VariableRateUpdate to the shared list private synchronized void addVru (VariableRateUpdate newVru) { pendingVrus.add(newVru); } // transfers the contents of the pending VRU list to the caller private synchronized List getVruList() { ArrayList result = new ArrayList<>(pendingVrus); pendingVrus.clear(); return result; } // ------------------ Helper stuff --------------- private void success (TariffUpdate update) { Broker broker = update.getBroker(); send(new TariffStatus(broker, update.getTariffId(), update.getId(), TariffStatus.Status.success)); } // sends a message to the broker private void send (TariffMessage msg) { if (null == msg) { log.debug("null outgoing message"); } else { brokerProxyService.sendMessage(msg.getBroker(), msg); } } private ValidationResult validateUpdate (TariffUpdate update) { Broker broker = update.getBroker(); Tariff tariff = tariffRepo.findTariffById(update.getTariffId()); if (tariff == null) { log.error("update - no such tariff " + update.getTariffId() + ", broker " + update.getBroker().getUsername()); return new ValidationResult(null, new TariffStatus(broker, update.getTariffId(), update.getId(), TariffStatus.Status.noSuchTariff)); } if (broker != tariff.getBroker()) { log.error("update - attempt by " + broker.getUsername() + " to revoke " + tariff.getBroker() + "'s tariff"); return new ValidationResult(null, new TariffStatus(broker, update.getTariffId(), update.getId(), TariffStatus.Status.invalidTariff)); } return new ValidationResult(tariff, new TariffStatus(broker, update.getTariffId(), update.getId(), TariffStatus.Status.success)); } // data structure for message validation result private class ValidationResult { Tariff tariff; TariffStatus message; ValidationResult (Tariff tariff, TariffStatus msg) { super(); this.tariff = tariff; this.message = msg; } } // data structure for pending subscription events private class PendingSubscription { Tariff tariff; CustomerInfo customer; int count; PendingSubscription (Tariff tariff, CustomerInfo customer, int count) { super(); this.tariff = tariff; this.customer = customer; this.count = count; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy