@Override
  @SuppressWarnings("unchecked")
  public Collection<? extends Product> findProducts(
      User user,
      ProductType productType,
      NestedProductTypeLocal nestedProductTypeLocal,
      ProductLocator productLocator) {
    if (logger.isDebugEnabled())
      logger.debug(
          "findProducts entered. organisationID = "
              + getOrganisationID()
              + " productType="
              + productType.getPrimaryKey());

    VoucherType vt = (VoucherType) productType;
    VoucherTypeLocal vtl = (VoucherTypeLocal) productType.getProductTypeLocal();
    int qty = nestedProductTypeLocal == null ? 1 : nestedProductTypeLocal.getQuantity();
    PersistenceManager pm = getPersistenceManager();

    Store store = Store.getStore(pm);
    // search for an available product
    Query q = pm.newQuery(Voucher.class);
    q.setFilter("productType == pProductType && productLocal.available");
    q.declareParameters("VoucherType pProductType");
    q.declareImports("import " + VoucherType.class.getName());
    Collection availableProducts =
        (Collection)
            q.execute(
                productType); // Product.getProducts(pm, this, ProductStatus.STATUS_AVAILABLE);
    ArrayList res = new ArrayList();
    Iterator iteratorAvailableProducts = availableProducts.iterator();
    for (int i = 0; i < qty; ++i) {
      Voucher product = null;
      if (iteratorAvailableProducts.hasNext()) {
        product = (Voucher) iteratorAvailableProducts.next();
        res.add(product);
      } else {
        // create products only if this product type is ours
        if (productType.getOrganisationID().equals(store.getOrganisationID())) {
          long createdProductCount = vtl.getCreatedVoucherCount();
          if (vtl.getMaxVoucherCount() < 0 || createdProductCount + 1 <= vtl.getMaxVoucherCount()) {
            product = new Voucher(vt, Product.createProductID());
            vtl.setCreatedVoucherCount(createdProductCount + 1);

            store.addProduct(user, product); // , (Repository)vt.getProductTypeLocal().getHome());
            res.add(product);
          }
        } // This productType is factored by this organisation
        else throw new UnsupportedOperationException("NYI");
      }
    }
    return res;
  }
  /** @return List of Public Places */
  @SuppressWarnings("unchecked")
  public List<Place> getPlacesPublic(Account account, String createdorder) {

    PersistenceManager pm = PMUtils.getPersistenceManager();
    try {

      // prepare query
      Query q = pm.newQuery(Place.class);
      q.setOrdering("type desc, createdOrder desc");
      q.setRange(0, account != null ? account.getFetchRows() : 20);
      q.declareImports("import com.hotf.server.model.Location.Type");

      List<Place> list;
      if (createdorder != null && createdorder.length() > 0) {

        // execute with filter
        q.declareParameters("String p_createdOrder");
        q.setFilter("type != null && type != 'START' && createdOrder >= p_createdOrder");
        String n = createdorder.toUpperCase();
        log.info("Getting more public places from the Datastore");
        list = (List<Place>) q.execute(n);

      } else {

        // execute without filter
        q.setFilter("type != null && type != 'START'");
        log.info("Getting public places from the Datastore");
        list = (List<Place>) q.execute();
      }

      return list;

    } catch (RuntimeException t) {

      log.severe(t.getMessage());

      // roll-back transactions and re-throw
      if (pm.currentTransaction().isActive()) {
        pm.currentTransaction().rollback();
      }

      throw t;
    }
  }
  @Override
  public List<SignalMetadata> getSignalList(
      Double minLatitude,
      Double minLongitude,
      Double maxLatitude,
      Double maxLongitude,
      List<String> carrierParams,
      List<String> phoneTypes,
      String clientId) {
    PersistenceManager manager = PMF.getManager();

    // If no carrier is specified, show points from all carriers
    if (carrierParams == null) {
      String[] carrierList = {"att", "verizon", "tmobile", "sprint"};
      carrierParams = Arrays.asList(carrierList);
    }

    // If no phoneType is specified, show points from all phoneTypes
    if (phoneTypes == null) {
      String[] phoneList = {"0", "1", "2", "3"};
      phoneTypes = Arrays.asList(phoneList);
    }

    // If user requests data for a region of size larger than 1.0000 by 1.0000 (about 11km by 11km),
    // only the center 1.0000 by 1.0000 of data will be returned
    if (maxLatitude - minLatitude > 1.0) {
      Double center = (maxLatitude + minLatitude) / 2.0;
      maxLatitude = center + .5;
      minLatitude = center - .5;
    }
    if (maxLongitude - minLongitude > 1.0) {
      Double centerl = (maxLongitude + minLongitude) / 2.0;
      maxLongitude = centerl + .5;
      minLongitude = centerl - .5;
    }

    try {
      ArrayList<SignalMetadata> signalInfo = new ArrayList<SignalMetadata>();

      // If clientId is absent, returns data within the given geographic box
      // for the given phoneType and carrier from the consolidated datastore
      if (clientId == null) {
        Query q = manager.newQuery(SignalInfoAvg.class);
        q.declareImports("import java.util.List");
        Object[] parameters = {minLatitude, maxLatitude, carrierParams, phoneTypes};
        q.declareParameters(
            "Double minLatitude, Double maxLatitude, List carrierParams, List phoneTypes");
        q.setFilter(
            "latitude >= minLatitude && latitude <= maxLatitude && carrierParams.contains(carrier) && phoneTypes.contains(phoneType)");

        List<SignalInfoAvg> allSignalInfo = (List<SignalInfoAvg>) q.executeWithArray(parameters);
        // Extent<SignalInfo> allSignalInfo = manager.getExtent(SignalInfo.class);

        ArrayList<SignalInfoAvg> filtered = new ArrayList<SignalInfoAvg>();
        for (SignalInfoAvg savg : allSignalInfo) {
          if (savg.getLongitude() >= minLongitude && savg.getLongitude() <= maxLongitude) {
            filtered.add(savg);
          }
        }

        for (SignalInfoAvg s : filtered) {
          signalInfo.add(s.getSignalMetadata());
        }

      } else {
        // If clientId is present, returns raw data of the given clientId,
        // phoneType, and carrier within the given geographic box
        Query q = manager.newQuery(SignalInfo.class);
        q.declareImports("import java.util.List");
        Object[] parameters = {minLatitude, maxLatitude, carrierParams, phoneTypes, clientId};
        q.declareParameters(
            "Double minLatitude, Double maxLatitude, List<String> carrierParams, List<String> phoneTypes, String myClientId");
        q.setFilter(
            "clientId == myClientId && latitude >= minLatitude && latitude <= maxLatitude && carrier == carrierParams && phoneType == phoneTypes");

        List<SignalInfo> allSignalInfo = (List<SignalInfo>) q.executeWithArray(parameters);
        // Extent<SignalInfo> allSignalInfo = manager.getExtent(SignalInfo.class);

        ArrayList<SignalInfo> filtered = new ArrayList<SignalInfo>();
        for (SignalInfo s : allSignalInfo) {
          if (s.getLongitude() >= minLongitude && s.getLongitude() <= maxLongitude) {
            filtered.add(s);
          }
        }

        for (SignalInfo s : filtered) {
          signalInfo.add(s.getSignalMetadata());
        }
      }

      return signalInfo;
    } finally {
      manager.close();
    }
  }