@Override
 protected boolean isDeleteAllowed(ServiceContext context, Entity entity) {
   Notification notification = (Notification) entity;
   Notification.State state = notification.getState();
   return !(state.equals(Notification.State.CREATED)
       || state.equals(Notification.State.STARTED)
       || state.equals(Notification.State.SCHEDULED));
 }
  @Override
  public ServiceResults postCollection(ServiceContext context) throws Exception {
    logger.info("NotificationService: start request.");
    Timer.Context timer = postTimer.time();
    postMeter.mark();
    try {
      validate(null, context.getPayload());
      Notification.PathTokens pathTokens =
          getPathTokens(context.getRequest().getOriginalParameters());
      context.getProperties().put("state", Notification.State.CREATED);
      context.getProperties().put("pathQuery", pathTokens);
      context.setOwner(sm.getApplication());
      ServiceResults results = super.postCollection(context);
      Notification notification = (Notification) results.getEntity();

      // update Notification properties
      if (notification.getStarted() == null || notification.getStarted() == 0) {
        long now = System.currentTimeMillis();
        notification.setStarted(System.currentTimeMillis());
        Map<String, Object> properties = new HashMap<String, Object>(2);
        properties.put("started", notification.getStarted());
        properties.put("state", notification.getState());
        notification.addProperties(properties);
        logger.info(
            "ApplicationQueueMessage: notification {} properties updated in duration {} ms",
            notification.getUuid(),
            System.currentTimeMillis() - now);
      }

      long now = System.currentTimeMillis();
      notificationQueueManager.queueNotification(notification, null);
      logger.info(
          "NotificationService: notification {} post queue duration {} ms ",
          notification.getUuid(),
          System.currentTimeMillis() - now);
      // future: somehow return 202?
      return results;
    } catch (Exception e) {
      logger.error("serialization failed", e);
      throw e;
    } finally {
      timer.stop();
    }
  }
  @Override
  public Entity updateEntity(ServiceRequest request, EntityRef ref, ServicePayload payload)
      throws Exception {

    validate(ref, payload);

    Notification notification = em.get(ref, Notification.class);

    if ("restart".equals(payload.getProperty("restart"))) { // for emergency
      // use only
      payload.getProperties().clear();
      payload.setProperty("restart", "");
      payload.setProperty("errorMessage", "");
      payload.setProperty("deliver", System.currentTimeMillis() + gracePeriod);

      // once finished, immutable
    } else if (notification.getFinished() != null) {
      throw new ForbiddenServiceOperationException(request, "Notification immutable once sent.");

      // once started, only cancel is allowed
    } else if (notification.getStarted() != null) {
      if (payload.getProperty("canceled") != Boolean.TRUE) {
        throw new ForbiddenServiceOperationException(
            request, "Notification has started. You may only set canceled.");
      }
      payload.getProperties().clear();
      payload.setProperty("canceled", Boolean.TRUE);
    }

    Entity response = super.updateEntity(request, ref, payload);

    Long deliver = (Long) payload.getProperty("deliver");
    if (deliver != null) {
      if (!deliver.equals(notification.getDeliver())) {
        notificationQueueManager.queueNotification((Notification) response, null);
      }
    }
    return response;
  }
  @Override
  public void sendNotification(
      String providerId, Object payload, Notification notification, TaskTracker tracker)
      throws Exception {
    try {
      List<TranslatedNotification> translatedNotifications = (List<TranslatedNotification>) payload;
      for (TranslatedNotification translatedNotification : translatedNotifications) {

        // set the optional TTL value used when pushing notifications
        WnsNotificationRequestOptional opt = new WnsNotificationRequestOptional();
        opt.ttl = String.valueOf(notification.getExpireTTLSeconds());

        switch (translatedNotification.getType()) {
          case "toast":
            WnsToast toast =
                new WnsToastBuilder()
                    .bindingTemplateToastText01(translatedNotification.getMessage().toString())
                    .build();
            service.pushToast(providerId, opt, toast);
            break;
          case "badge":
            WnsBadge badge;
            if (translatedNotification.getMessage() instanceof Integer) {
              badge =
                  new WnsBadgeBuilder()
                      .value((Integer) translatedNotification.getMessage())
                      .build();
            } else {
              badge =
                  new WnsBadgeBuilder()
                      .value(translatedNotification.getMessage().toString())
                      .build();
            }
            service.pushBadge(providerId, opt, badge);
            break;
          case "raw":
            Object message = translatedNotification.getMessage();
            if (message instanceof String) {
              WnsRaw raw = new WnsRawBuilder().stream(((String) message).getBytes()).build();

              // set additional optional parameter for raw notifications
              opt.cachePolicy = "cache";
              opt.requestForStatus = "true";

              WnsNotificationResponse response = service.pushRaw(providerId, opt, raw);
              if (!response.notificationStatus.equals(
                  "received")) { // https://msdn.microsoft.com/en-us/library/windows/apps/hh465435.aspx#pncodes_x_wns_notification
                throw new Exception(
                    String.format(
                        "Notification failed status:%s, devicesStatus:%s, description:%s, debug flag:%s",
                        response.notificationStatus,
                        response.deviceConnectionStatus,
                        response.errorDescription,
                        response.debugTrace));
              }
            } else {
              throw new IllegalArgumentException(
                  "You must send a string in the raw body. instead got this: "
                      + message.getClass().getName());
            }
            break;
          default:
            throw new IllegalArgumentException(
                translatedNotification.getType()
                    + " does not match a valid notification type (toast,badge).");
        }
      }
      tracker.completed();
    } catch (Exception e) {
      tracker.failed(0, e.toString());
      LOG.error("Failed to send notification", e);
    }
  }