public void record(FeedMessage feedMessage) throws SQLException {

    int numAlerts = 0;
    int numTripUpdates = 0;
    int numVehiclePositions = 0;

    mOpenQueries = 0;

    for (FeedEntity entity : feedMessage.getEntityList()) {
      if (entity.hasAlert()) {
        numAlerts++;
      }

      if (entity.hasTripUpdate()) {
        numTripUpdates++;
      }

      if (entity.hasVehicle()) {
        numVehiclePositions++;
      }
    }

    boolean hasAlerts = numAlerts > 0;
    boolean hasTripUpdates = numTripUpdates > 0;
    boolean hasVehiclePositions = numVehiclePositions > 0;

    mLogger.info(
        String.format(
            "Entities: alerts=%d, updates=%d, positions=%d",
            numAlerts, numTripUpdates, numVehiclePositions));

    mLogger.info("Clearing tables...");

    if (hasAlerts) {
      clearAlertsData();
    }

    if (hasTripUpdates) {
      clearTripUpdatesData();
    }

    if (hasVehiclePositions) {
      clearVehiclePositionsData();
    }

    mLogger.info("Finished clearing tables");

    if (!hasAlerts && !hasTripUpdates && !hasVehiclePositions) {
      mLogger.info("Nothing to record");
      return;
    }

    boolean useCopy = mConnection instanceof BaseConnection;

    CopyManager cm = null;

    DataCopier tuCopier = null;
    DataCopier stCopier = null;
    DataCopier vpCopier = null;

    CopyIn tuCopyIn = null;
    CopyIn stCopyIn = null;
    CopyIn vpCopyIn = null;

    if (useCopy) {
      cm = new CopyManager((BaseConnection) mConnection);
      tuCopier = new DataCopier();
      stCopier = new DataCopier();
      vpCopier = new DataCopier();

      if (hasTripUpdates) {
        stCopyIn = cm.copyIn(COPY_TRIP_UPDATES_STOP_TIMES);
        mOpenQueries++;

        stCopier = new DataCopier(stCopyIn, COPY_SEPARATOR);
      } else if (hasVehiclePositions) {
        vpCopyIn = cm.copyIn(COPY_VEHICLE_POSITIONS);
        mOpenQueries++;

        vpCopier = new DataCopier(vpCopyIn, COPY_SEPARATOR);
      }
    }

    for (FeedEntity entity : feedMessage.getEntityList()) {
      if (entity.hasAlert()) {
        try {
          recordAlert(entity.getAlert());
        } catch (SQLException e) {
          mLogger.warning(getString(e));
        }
      }

      if (entity.hasTripUpdate()) {
        try {
          recordTripUpdate(entity.getTripUpdate(), tuCopier, stCopier);
        } catch (Exception e) {
          mLogger.warning(getString(e));
        }
      }

      if (entity.hasVehicle()) {
        try {
          recordVehicle(entity.getVehicle(), vpCopier);
        } catch (Exception e) {
          mLogger.warning(getString(e));
        }
      }
    }

    if (hasAlerts) {
      mLogger.info("Committing alerts... ");

      try {
        mStatements.get(STALERT).executeBatch();
        mStatements.get(STALERT_ENTITIES).executeBatch();
        mStatements.get(STALERT_TIMERANGES).executeBatch();
        mLogger.info("done");
      } catch (Exception e) {
        mLogger.warning(getString(e));
      }
    }

    if (hasTripUpdates) {
      mLogger.info("Committing trip updates... ");

      try {
        if (stCopier == null) {
          mStatements.get(STTRIPUPDATE_STOPTIMEUPDATES).executeBatch();
        } else if (stCopyIn == null && stCopier.size() > 0) {
          stCopyIn = cm.copyIn(COPY_TRIP_UPDATES_STOP_TIMES);
          mOpenQueries++;

          stCopier.write(stCopyIn, COPY_SEPARATOR);
        }
      } catch (SQLException e) {
        e.printStackTrace();
      }

      if (stCopyIn != null) {
        try {
          stCopyIn.endCopy();
          mOpenQueries--;
        } catch (Exception e) {
          mLogger.warning(getString(e));
        }
      }

      try {
        if (tuCopier == null) {
          mStatements.get(STTRIPUPDATE).executeBatch();
        } else if (tuCopyIn == null && tuCopier.size() > 0) {
          tuCopyIn = cm.copyIn(COPY_TRIP_UPDATES);
          mOpenQueries++;

          tuCopier.write(tuCopyIn, COPY_SEPARATOR);
        }
      } catch (SQLException e) {
        e.printStackTrace();
      }

      if (tuCopyIn != null) {
        try {
          tuCopyIn.endCopy();
          mOpenQueries--;
        } catch (Exception e) {
          mLogger.warning(getString(e));
        }
      }

      mLogger.info("done");
    }

    if (hasVehiclePositions) {
      System.err.print("Committing vehicle positions... ");

      try {
        if (vpCopier == null) {
          mStatements.get(STVEHICLE).executeBatch();
        } else if (vpCopyIn == null && vpCopier.size() > 0) {
          vpCopyIn = cm.copyIn(COPY_VEHICLE_POSITIONS);
          mOpenQueries++;
          vpCopier.write(vpCopyIn, COPY_SEPARATOR);
        }
      } catch (Exception e) {
        mLogger.warning(getString(e));
      }

      if (vpCopyIn != null) {
        vpCopyIn.endCopy();
        mOpenQueries--;
      }

      mLogger.info("done");
    }
  }
  /**
   * This method downloads the latest vehicle data, processes each vehicle in turn, and create a
   * GTFS-realtime feed of trip updates and vehicle positions as a result.
   */
  private void refreshVehicles() throws IOException, JSONException {

    /** We download the vehicle details as an array of JSON objects. */
    JSONArray vehicleArray = downloadVehicleDetails();

    /**
     * The FeedMessage.Builder is what we will use to build up our GTFS-realtime feeds. We create a
     * feed for both trip updates and vehicle positions.
     */
    FeedMessage.Builder tripUpdates = GtfsRealtimeLibrary.createFeedMessageBuilder();
    FeedMessage.Builder vehiclePositions = GtfsRealtimeLibrary.createFeedMessageBuilder();

    /** We iterate over every JSON vehicle object. */
    for (int i = 0; i < vehicleArray.length(); ++i) {

      JSONObject obj = vehicleArray.getJSONObject(i);
      String trainNumber = obj.getString("trainno");
      String route = obj.getString("dest");
      String stopId = obj.getString("nextstop");
      double lat = obj.getDouble("lat");
      double lon = obj.getDouble("lon");
      int delay = obj.getInt("late");

      /**
       * We construct a TripDescriptor and VehicleDescriptor, which will be used in both trip
       * updates and vehicle positions to identify the trip and vehicle. Ideally, we would have a
       * trip id to use for the trip descriptor, but the SEPTA api doesn't include it, so we settle
       * for a route id instead.
       */
      TripDescriptor.Builder tripDescriptor = TripDescriptor.newBuilder();
      tripDescriptor.setRouteId(route);

      VehicleDescriptor.Builder vehicleDescriptor = VehicleDescriptor.newBuilder();
      vehicleDescriptor.setId(trainNumber);

      /**
       * To construct our TripUpdate, we create a stop-time arrival event for the next stop for the
       * vehicle, with the specified arrival delay. We add the stop-time update to a TripUpdate
       * builder, along with the trip and vehicle descriptors.
       */
      StopTimeEvent.Builder arrival = StopTimeEvent.newBuilder();
      arrival.setDelay(delay * 60);

      StopTimeUpdate.Builder stopTimeUpdate = StopTimeUpdate.newBuilder();
      stopTimeUpdate.setArrival(arrival);
      stopTimeUpdate.setStopId(stopId);

      TripUpdate.Builder tripUpdate = TripUpdate.newBuilder();
      tripUpdate.addStopTimeUpdate(stopTimeUpdate);
      tripUpdate.setTrip(tripDescriptor);
      tripUpdate.setVehicle(vehicleDescriptor);

      /**
       * Create a new feed entity to wrap the trip update and add it to the GTFS-realtime trip
       * updates feed.
       */
      FeedEntity.Builder tripUpdateEntity = FeedEntity.newBuilder();
      tripUpdateEntity.setId(trainNumber);
      tripUpdateEntity.setTripUpdate(tripUpdate);
      tripUpdates.addEntity(tripUpdateEntity);

      /**
       * To construct our VehiclePosition, we create a position for the vehicle. We add the position
       * to a VehiclePosition builder, along with the trip and vehicle descriptors.
       */
      Position.Builder position = Position.newBuilder();
      position.setLatitude((float) lat);
      position.setLongitude((float) lon);

      VehiclePosition.Builder vehiclePosition = VehiclePosition.newBuilder();
      vehiclePosition.setPosition(position);
      vehiclePosition.setTrip(tripDescriptor);
      vehiclePosition.setVehicle(vehicleDescriptor);

      /**
       * Create a new feed entity to wrap the vehicle position and add it to the GTFS-realtime
       * vehicle positions feed.
       */
      FeedEntity.Builder vehiclePositionEntity = FeedEntity.newBuilder();
      vehiclePositionEntity.setId(trainNumber);
      vehiclePositionEntity.setVehicle(vehiclePosition);
      vehiclePositions.addEntity(vehiclePositionEntity);
    }

    /** Build out the final GTFS-realtime feed messagse and save them. */
    _gtfsRealtimeProvider.setTripUpdates(tripUpdates.build());
    _gtfsRealtimeProvider.setVehiclePositions(vehiclePositions.build());

    _log.info("vehicles extracted: " + tripUpdates.getEntityCount());
  }