Package org.onebusaway.transit_data_federation.impl.realtime

Source Code of org.onebusaway.transit_data_federation.impl.realtime.VehicleLocationRecordCacheImpl$CacheEvictionHandler

/**
* Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org>
*
* 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.onebusaway.transit_data_federation.impl.realtime;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.onebusaway.collections.ConcurrentCollectionsLibrary;
import org.onebusaway.gtfs.model.AgencyAndId;
import org.onebusaway.realtime.api.VehicleLocationRecord;
import org.onebusaway.transit_data_federation.services.blocks.BlockInstance;
import org.onebusaway.transit_data_federation.services.blocks.ScheduledBlockLocation;
import org.onebusaway.transit_data_federation.services.realtime.ScheduleDeviationSamples;
import org.onebusaway.transit_data_federation.services.realtime.VehicleLocationCacheElements;
import org.onebusaway.transit_data_federation.services.realtime.VehicleLocationCacheEntry;
import org.onebusaway.transit_data_federation.services.realtime.VehicleLocationRecordCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
* The general assumption is that we won't get back to back updates from the
* same vehicle on a short enough time-scale that it can cause serious race
* condition issues. If we DO get lots of back to back updates, we'll just have
* to assume some amount of data loss.
*
* @param record record to add
*/
@Component
class VehicleLocationRecordCacheImpl implements VehicleLocationRecordCache {

  private static Logger _log = LoggerFactory.getLogger(VehicleLocationRecordCacheImpl.class);

  private ConcurrentMap<AgencyAndId, VehicleLocationCacheEntry> _entriesByVehicleId = new ConcurrentHashMap<AgencyAndId, VehicleLocationCacheEntry>();

  private ConcurrentMap<BlockInstance, Set<AgencyAndId>> _vehicleIdsByBlockInstance = new ConcurrentHashMap<BlockInstance, Set<AgencyAndId>>();

  /**
   * By default, we keep around 20 minutes of cache entries
   */
  private int _blockLocationRecordCacheWindowSize = 20 * 60;

  private int _cacheEvictionFrequency = 1;

  private ScheduledExecutorService _executor;

  private ScheduledFuture<?> _evictionHandler;

  /**
   * Controls how far back in time we include records in the
   * {@link BlockLocationRecordCollection} for each active trip.
   *
   * @param windowSize in seconds
   */
  public void setBlockLocationRecordCacheWindowSize(int windowSize) {
    _blockLocationRecordCacheWindowSize = windowSize;
  }

  /**
   *
   * @param cacheEvictionFrequency frequency, in minutes
   */
  public void setCacheEvictionFrequency(int cacheEvictionFrequency) {
    _cacheEvictionFrequency = cacheEvictionFrequency;
  }

  @PostConstruct
  public void start() {
    _executor = Executors.newScheduledThreadPool(1);
    _evictionHandler = _executor.scheduleAtFixedRate(
        new CacheEvictionHandler(), _cacheEvictionFrequency,
        _cacheEvictionFrequency, TimeUnit.MINUTES);
  }

  @PreDestroy
  public void stop() {
    if (_evictionHandler != null)
      _evictionHandler.cancel(true);
    if (_executor != null)
      _executor.shutdownNow();
  }

  /****
   * {@link VehicleLocationRecordCache} Interface
   ****/

  @Override
  public VehicleLocationCacheElements getRecordForVehicleId(
      AgencyAndId vehicleId) {
    VehicleLocationCacheEntry entry = _entriesByVehicleId.get(vehicleId);
    if (entry == null)
      return null;
    return entry.getElements();
  }

  @Override
  public List<VehicleLocationCacheElements> getRecordsForBlockInstance(
      BlockInstance blockInstance) {

    Set<AgencyAndId> vehicleIds = _vehicleIdsByBlockInstance.get(blockInstance);

    List<VehicleLocationCacheElements> records = new ArrayList<VehicleLocationCacheElements>();
    if (vehicleIds != null) {
      for (AgencyAndId vehicleId : vehicleIds) {
        VehicleLocationCacheEntry record = _entriesByVehicleId.get(vehicleId);

        if (record != null && record.getBlockInstance().equals(blockInstance))
          records.add(record.getElements());
      }
    }

    return records;
  }

  @Override
  public VehicleLocationCacheElements addRecord(BlockInstance blockInstance,
      VehicleLocationRecord record,
      ScheduledBlockLocation scheduledBlockLocation,
      ScheduleDeviationSamples samples) {

    AgencyAndId vehicleId = record.getVehicleId();

    while (true) {

      VehicleLocationCacheEntry newCacheEntry = new VehicleLocationCacheEntry(
          blockInstance);

      VehicleLocationCacheEntry cacheEntry = _entriesByVehicleId.putIfAbsent(
          vehicleId, newCacheEntry);

      if (cacheEntry == null) {

        cacheEntry = newCacheEntry;

        /**
         * Since we're adding a new entry, we indicate the connection between
         * this block instance and vehicleId
         */
        ConcurrentCollectionsLibrary.addToMapValueSet(
            _vehicleIdsByBlockInstance, blockInstance, vehicleId);
      }

      /**
       * If the block instance of a vehicle has changed mid-stream, we close off
       * the cache entry and remove the block=>vid mapping
       */
      if (cacheEntry.isClosedBecauseBlockInstanceChanged(blockInstance)) {

        _entriesByVehicleId.remove(vehicleId);

        ConcurrentCollectionsLibrary.removeFromMapValueSet(
            _vehicleIdsByBlockInstance, cacheEntry.getBlockInstance(),
            vehicleId);

        continue;
      }

      /**
       * If the element failed to add because the entry is closed, we loop.
       * Someone closed the entry while we were in the process of requesting it
       * from the map. On the next loop, it should no longer be in the map.
       */
      if (!cacheEntry.addElement(record, scheduledBlockLocation, samples))
        continue;

      BlockInstance existingBlockInstance = cacheEntry.getBlockInstance();
      if (!blockInstance.equals(existingBlockInstance))
        ConcurrentCollectionsLibrary.removeFromMapValueSet(
            _vehicleIdsByBlockInstance, existingBlockInstance, vehicleId);

      // Ensure the block => vehicle mapping is set

      return cacheEntry.getElements();

    }
  }

  @Override
  public void clearRecordsForVehicleId(AgencyAndId vehicleId) {

    VehicleLocationCacheEntry record = _entriesByVehicleId.remove(vehicleId);

    if (record != null) {
      ConcurrentCollectionsLibrary.removeFromMapValueSet(
          _vehicleIdsByBlockInstance, record.getBlockInstance(), vehicleId);
    }
  }

  public void clearStaleRecords(long time) {

    Iterator<Entry<AgencyAndId, VehicleLocationCacheEntry>> it = _entriesByVehicleId.entrySet().iterator();

    while (it.hasNext()) {

      Entry<AgencyAndId, VehicleLocationCacheEntry> entry = it.next();
      AgencyAndId vehicleId = entry.getKey();
      VehicleLocationCacheEntry cacheEntry = entry.getValue();

      if (cacheEntry.closeIfStale(time)) {

        if (_log.isDebugEnabled())
          _log.debug("pruning block location record cache for vehicle="
              + vehicleId + " block=" + cacheEntry.getBlockInstance());
        it.remove();
        ConcurrentCollectionsLibrary.removeFromMapValueSet(
            _vehicleIdsByBlockInstance, cacheEntry.getBlockInstance(),
            vehicleId);
      }
    }
  }

  /****
   * Private Methods
   ****/

  private class CacheEvictionHandler implements Runnable {

    @Override
    public void run() {
      clearStaleRecords(System.currentTimeMillis()
          - _blockLocationRecordCacheWindowSize * 1000);
    }
  }

}
TOP

Related Classes of org.onebusaway.transit_data_federation.impl.realtime.VehicleLocationRecordCacheImpl$CacheEvictionHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.