/* * © Copyright IBM Corp. 2014 * * 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 com.ibm.sbt.provisioning.sample.app.task; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.logging.Logger; import com.ibm.commons.util.io.json.JsonException; import com.ibm.commons.util.io.json.JsonJavaFactory; import com.ibm.commons.util.io.json.JsonJavaObject; import com.ibm.commons.util.io.json.JsonParser; import com.ibm.sbt.provisioning.sample.app.WeightManager; import com.ibm.sbt.provisioning.sample.app.model.SubscriptionEntitlement; import com.ibm.sbt.provisioning.sample.app.model.SubscriptionEntitlement.NotesType; import com.ibm.sbt.provisioning.sample.app.weightedBSSCall.WeightedBSSCall; import com.ibm.sbt.provisioning.sample.app.weightedBSSCall.authentication.ChangePassword; import com.ibm.sbt.provisioning.sample.app.weightedBSSCall.authentication.SetOneTimePassword; import com.ibm.sbt.provisioning.sample.app.weightedBSSCall.subscriber.ActivateSubscriber; import com.ibm.sbt.provisioning.sample.app.weightedBSSCall.subscriber.AddSubscriber; import com.ibm.sbt.provisioning.sample.app.weightedBSSCall.subscriber.AddSubscriberSuppressEmail; import com.ibm.sbt.provisioning.sample.app.weightedBSSCall.subscriber.EntitleSubscriber; import com.ibm.sbt.provisioning.sample.app.weightedBSSCall.subscriber.GetSubscriber; import com.ibm.sbt.provisioning.sample.app.weightedBSSCall.subscriber.UpdateNotesSubscriber; import com.ibm.sbt.services.client.ClientServicesException; import com.ibm.sbt.services.client.base.JsonEntity; /** * This class IS-A <code>Runnable</code>. It represents a task whose <code>run()</code> method could be potentially executed in its own * separated thread of execution. * The <code>run()</code> method permits the state transition of the object by mean of calls to the BSS API . * Those calls are encapsulated each in their own method and triggered by mean of invocation of the <code>call()</code> * method of the <code>abstract class WeightebBSSCall</code> on instances of the classes belonging to the * package <code>com.ibm.sbt.provisioning.sample.app.weightedBSSCall</code>. * */ public class SubscriberTask implements Runnable { private static final Logger logger = Logger.getLogger(SubscriberTask.class.getName()); public enum State { SUBSCRIBER_NON_EXISTENT, SUBSCRIBER_ADDED, SUBSCRIBER_ACTIVE, SUBSCRIBER_ONE_TIME_PWD_SET, SUBSCRIBER_PASSWORD_CHANGED, SUBSCRIBER_UPDATED, SUBSCRIBER_ENTITLED, SUBSCRIBE_FAILED, SEAT_ASSIGNED } private JsonJavaObject subscriber ; private String subscriberEmail ; private String customerId ; private List<SubscriptionEntitlement> entitlements; private State status ; private String subscriberId ; private String oneTimePassword; private String newPassword; private int attemptCount = 0; private int attempts; private boolean attemptsFlag = false; private boolean suppressEmail = false; /** * Each subscriberTask has a key made up by the subscriberEmail, column, subscriptionId * */ private String taskKey ; public SubscriberTask(JsonJavaObject subscriber, String customerId, List<SubscriptionEntitlement> entitlements, State initialStatus, String newPassword){ this.subscriber = subscriber; this.customerId = customerId; this.entitlements = entitlements; this.status = initialStatus; this.oneTimePassword = "onet1me!"; this.newPassword = newPassword; this.subscriberEmail = this.subscriber.getAsObject("Subscriber").getAsObject("Person").getAsString("EmailAddress"); this.taskKey = this.subscriberEmail; } public SubscriberTask(JsonJavaObject subscriber, String customerId, List<SubscriptionEntitlement> entitlements, State initialStatus, String newPassword, int attempts){ this.subscriber = subscriber; this.customerId = customerId; this.entitlements = entitlements; this.status = initialStatus; this.oneTimePassword = "onet1me!"; this.newPassword = newPassword; this.subscriberEmail = this.subscriber.getAsObject("Subscriber").getAsObject("Person").getAsString("EmailAddress"); this.taskKey = this.subscriberEmail; this.attempts = attempts; this.attemptsFlag = true; } public SubscriberTask(JsonJavaObject subscriber, String customerId, String subscriptionId, State initialStatus) { this.subscriber = subscriber ; this.customerId = customerId ; this.status = initialStatus ; this.oneTimePassword = "onet1me!"; this.subscriberEmail = this.subscriber.getAsObject("Subscriber").getAsObject("Person").getAsString("EmailAddress"); this.taskKey = this.subscriberEmail; } public SubscriberTask(JsonJavaObject subscriber, String customerId, String subscriptionId, State initialStatus, int attempts) { this.subscriber = subscriber ; this.customerId = customerId ; this.status = initialStatus ; this.oneTimePassword = "onet1me!"; this.subscriberEmail = this.subscriber.getAsObject("Subscriber").getAsObject("Person").getAsString("EmailAddress"); this.taskKey = this.subscriberEmail; this.attempts = attempts; this.attemptsFlag = true; } /** * Business logic of the task * <p> * It will guide the subscriber through its state transition, from <code>SUBSCRIBER_NON_EXISTENT</code> * till <code>SEAT_ASSIGNED</code>, by mean of call to the BSS API. * The logic needed for triggering the HTTP calls to the BSS API is encapsulated in the <code>doCall()</code> * method implementation of the classes that belong to the package <code>com.ibm.sbt.provisioning.sample.app.weightedBSSCall</code>. * This logic is triggered by mean of invocation of the <code>call()</code> method of the <code>abstract * class WeightedBSSCall</code> on the instances of the classes belonging to that package */ @Override public void run() { Thread.currentThread().setName("SubscriberTask: "+ this.taskKey ); boolean success = true ; while( success == true ){ if (this.status == State.SUBSCRIBER_NON_EXISTENT) { if(suppressEmail){ this.subscriberId = addSubscriberSuppressEmail(); }else{ this.subscriberId = addSubscriber(); } if( this.subscriberId != null ) { this.status = State.SUBSCRIBER_ADDED ; }else{ success = false ; } } else if (this.status == State.SUBSCRIBER_ADDED) { if(updateSubscriber()){ this.status = State.SUBSCRIBER_UPDATED; }else{ success = false; } } else if (this.status == State.SUBSCRIBER_UPDATED) { if( activateSubscriber() ){ this.status = State.SUBSCRIBER_ACTIVE ; }else{ success = false ; } } else if ( this.status == State.SUBSCRIBER_ACTIVE ) { if (setOneTimePassword()){ this.status = State.SUBSCRIBER_ONE_TIME_PWD_SET ; }else{ success = false ; } } else if(this.newPassword == null){ // If the new password is null, we don't change it, the user will change it on first login // If the new password is not null, we change it this.status = State.SUBSCRIBER_PASSWORD_CHANGED; } else if( this.status == State.SUBSCRIBER_ONE_TIME_PWD_SET ){ if(changePassword()){ this.status = State.SUBSCRIBER_PASSWORD_CHANGED; }else{ success = false; } } else if(this.status == State.SUBSCRIBER_PASSWORD_CHANGED){ if( entitleSubscriber() ){ this.status = State.SUBSCRIBER_ENTITLED ; }else{ success = false ; } } else if( this.status == State.SUBSCRIBER_ENTITLED ){ if( waitForSeatAssignment() ){ this.status = State.SEAT_ASSIGNED ; }else{ success = false ; } } else if( this.status == State.SEAT_ASSIGNED){ success = false ; attemptsFlag = false; } else if( this.status == State.SUBSCRIBE_FAILED){ success = false ; attemptsFlag = false; logger.info("Checking for task completion..."); logger.info("Completed: "+checkForFinish()); } } if(attemptsFlag){ if(success==false){ attemptCount++; logger.info("Attempts = " + attemptCount+"/"+attempts); logger.info("Waiting 30 seconds before retrying task..."); try { Thread.sleep(1000*30); } catch (InterruptedException e) { logger.severe(e.getMessage()); } } if(attemptCount>=attempts&&this.status != State.SEAT_ASSIGNED){ this.status = State.SUBSCRIBE_FAILED; BSSProvisioning.failedSubscriptions.add(this); BSSProvisioning.incrementSubscribersFailed(); logger.info("Checking for task completion..."); logger.info("Completed: "+checkForFinish()); } } logger.info("Task execution exiting with status: "+ this.status.name() ); if( this.status == State.SEAT_ASSIGNED ){ logger.info("Task exited." ); }else{ BSSProvisioning.getSubscribersTasks().add(this); logger.info("Re-queuing it..." ); } } @SuppressWarnings("unchecked") private String identifySubscriberState(){ WeightedBSSCall<JsonEntity> getSubscriber = new GetSubscriber(subscriberEmail, null); JsonEntity subscriberEntity = null; try{ subscriberEntity = getSubscriber.call(); String subscriberState = subscriberEntity.getAsString("SubscriberState"); if(subscriberState.equalsIgnoreCase("PENDING")){ this.status = State.SUBSCRIBER_ADDED; }else if(subscriberState.equalsIgnoreCase("ACTIVE")){ ArrayList<Object> seatSetList = (ArrayList<Object>) subscriberEntity.getAsObject("SeatSet"); if(seatSetList.size() > 0){ for(Object seatObject:seatSetList){ JsonJavaObject seatSet = ((JsonJavaObject) seatObject); String seatState = seatSet.getAsString("SeatState"); if(seatState.equalsIgnoreCase("ENTITLE_PENDING")){ this.status = State.SUBSCRIBER_ENTITLED; }else if(seatState.equalsIgnoreCase("ASSIGNED")){ this.status = State.SEAT_ASSIGNED; } } } } this.subscriberId = subscriberEntity.getAsLong("Id").toString(); }catch(Exception e){ logger.severe(e.getClass() + " : " + e.getMessage()); } return this.subscriberId; } /** * This method will trigger the adding of a subscriber to an organization by mean of invocation of the <code>call()</code> method of the <code>abstract * class WeightedBSSCall</code> on an instance of the <code>AddSubscriber</code> class * <p> * @return the BSS subscriber identifier */ private String addSubscriber(){ String subscriberId = null ; if( this.subscriber != null ){ ((JsonJavaObject)this.subscriber.get("Subscriber")).putString("CustomerId", customerId); WeightedBSSCall<String> addSubscriber = new AddSubscriber(this.subscriber); logger.info("Adding subscriber..."); try{ subscriberId = addSubscriber.call(); } catch (Exception e) { logger.severe(e.getClass()+" : " + e.getMessage()); String responseBody = ((ClientServicesException) e.getCause()).getResponseBody(); if(emailAlreadyExists(responseBody)){ subscriberId = identifySubscriberState(); } } } return subscriberId ; } /** * This method will trigger the adding of a subscriber w/ suppress email to an organization by mean of invocation of the <code>call()</code> method of the <code>abstract * class WeightedBSSCall</code> on an instance of the <code>AddSubscriber</code> class * <p> * @return the BSS subscriber identifier */ private String addSubscriberSuppressEmail(){ String subscriberId = null ; if( this.subscriber != null ){ ((JsonJavaObject)this.subscriber.get("Subscriber")).putString("CustomerId", customerId); WeightedBSSCall<String> addSubscriberSuppressEmail = new AddSubscriberSuppressEmail(this.subscriber); logger.info("Adding subscriber with suppressed email..."); try{ subscriberId = addSubscriberSuppressEmail.call(); } catch (Exception e) { logger.severe(e.getClass()+" : " + e.getMessage()); String responseBody = ((ClientServicesException) e.getCause()).getResponseBody(); if(emailAlreadyExists(responseBody)){ subscriberId = identifySubscriberState(); } } } return subscriberId ; } private boolean emailAlreadyExists(String errorResponse){ try{ JsonJavaObject json = (JsonJavaObject) JsonParser.fromJson(JsonJavaFactory.instanceEx2, errorResponse); JsonJavaObject bssResponse = json.getAsObject("BSSResponse"); String responseMessage = bssResponse.getAsString("ResponseMessage"); return responseMessage.equalsIgnoreCase("The email address already exists"); }catch(JsonException e){ logger.severe("Error parsing JSON"); } return false; } /** * This method will trigger the subscriber activation by mean of invocation of the <code>call()</code> method of the <code>abstract * class WeightedBSSCall</code> on an instance of the <code>ActivateSubscriber</code> class * <p> * @return <code>true</code> if activated, <code>false</code> otherwise */ private boolean activateSubscriber(){ Boolean subscriberActive = false ; WeightedBSSCall<Boolean> activateSubscriber = new ActivateSubscriber(this.subscriberId,this.subscriberEmail); try{ logger.info("Activating subscriber..."); subscriberActive = activateSubscriber.call(); if( subscriberActive == null ){ subscriberActive = false ; } } catch (Exception e) { logger.severe(e.getClass()+" : " + e.getMessage()); } return subscriberActive ; } /** * This method will trigger the subscriber one time password setting by mean of invocation of the <code>call()</code> method of the <code>abstract * class WeightedBSSCall</code> on an instance of the <code>SetOneTimePassword</code> class * <p> * @return <code>true</code> if the one time password is set, <code>false</code> otherwise */ private boolean setOneTimePassword(){ Boolean oneTimePasswordSet = false ; if( this.subscriber != null ){ WeightedBSSCall<Boolean> setOneTimePassword = new SetOneTimePassword(this.subscriberEmail , this.oneTimePassword); try{ logger.info("Setting one time password ..."); oneTimePasswordSet = setOneTimePassword.call(); if( oneTimePasswordSet == null ){ oneTimePasswordSet = false ; } } catch (Exception e) { logger.severe(e.getClass()+" : " + e.getMessage()); } } return oneTimePasswordSet ; } /** * This method will trigger the subscriber password changing by mean of invocation of the * {@link com.ibm.sbt.provisioning.sample.app.weightedBSSCall.WeightedBSSCall#call()} method on an * instance of the * {@link com.ibm.sbt.provisioning.sample.app.weightedBSSCall.authentication.ChangePassword} class * <p> * * @param email email used as user credential of the subscriber<br> * @param oneTimePassword string representing the one time password<br> * @param newPassword string representing the new password<br> * @return <code>true</code> if the password is changed, <code>false</code> otherwise */ private boolean changePassword(){ Boolean passwordChanged = false; logger.info("Subscriber changing password ..."); WeightedBSSCall<Boolean> changePassword = new ChangePassword(this.subscriberEmail, this.oneTimePassword, this.newPassword); try { passwordChanged = changePassword.call(); } catch(Exception e){ logger.severe(e.getClass() + " : " + e.getMessage()); } return passwordChanged; } /** * This method will trigger the adding of a subscriber to an organization by mean of invocation of * the <code>call()</code> method of the <code>abstract * class WeightedBSSCall</code> on an instance of the <code>AddSubscriber</code> class * <p> * * @return the BSS subscriber identifier */ private boolean updateSubscriber(){ boolean updated = false; if(this.subscriber != null){ List<NotesType> addedMailAttributes = new ArrayList<NotesType>(); for(SubscriptionEntitlement entitlement:entitlements){ NotesType entitlementNotesType = entitlement.getNotesType(); if(!addedMailAttributes.contains(entitlementNotesType)){ addedMailAttributes.add(entitlementNotesType); WeightedBSSCall<Boolean> updateSubscriber = new UpdateNotesSubscriber(this.subscriber, entitlementNotesType); logger.info("Updating subscriber..."); try{ updated = updateSubscriber.call(); }catch(Exception e){ logger.severe(e.getClass() + " : " + e.getMessage()); } } } } return updated; } private String getSubscriberState(){ WeightedBSSCall<JsonEntity> getSubscriber = new GetSubscriber(subscriberEmail, null); JsonEntity subscriberEntity = null; try{ subscriberEntity = getSubscriber.call(); }catch(Exception e){ logger.severe(e.getMessage()); } return subscriberEntity.getAsString("SubscriberState"); } /** * This method will trigger the entitlement of the subscriber to the subscription by mean of invocation of the <code>call()</code> method of the <code>abstract * class WeightedBSSCall</code> on an instance of the <code>EntitleSubscriber</code> class * <p> * @return <code>true</code> if the subscriber is entitled, <code>false</code> otherwise */ private boolean entitleSubscriber() { Boolean subscriberEntitled = false; logger.info("Entitling subscriber..."); String subscriberState = getSubscriberState(); if (!"ACTIVE".equals(subscriberState)) { return subscriberEntitled; } for (SubscriptionEntitlement entitlement : entitlements) { WeightedBSSCall<Boolean> entitleSubscriber = new EntitleSubscriber( this.subscriberId, entitlement.getSubscriptionId(), this.subscriberEmail); try { subscriberEntitled = entitleSubscriber.call(); if (subscriberEntitled == null) { subscriberEntitled = false; } if (!subscriberEntitled) { // stop when any entitle subscriber fails logger.severe("Unable to entitle subscriber: " + subscriberState + " to subscription: " + entitlement.getPartNumber()); break; } } catch (Exception e) { logger.severe(e.getClass() + " : " + e.getMessage()); } } return subscriberEntitled; } /** * This method will trigger the retrieval of the subscriber by mean of invocation of the <code>call()</code> method of the <code>abstract * class WeightedBSSCall</code> on an instance of the <code>GetSubscriber</code> class * <p>. * @return <code>true</code> if a seat from the subscription has been assigned to the subscriber, <code>false</code> otherwise */ private boolean waitForSeatAssignment(){ boolean seatsAssigned = false; if( this.subscriberId != null ){ // SUBSCRIBER RETRIEVAL String suscriberEmail = this.subscriber.getAsObject("Subscriber").getAsObject("Person").getAsString("EmailAddress"); logger.info("Subscriber retrieval by email : " + suscriberEmail ); WeightedBSSCall<JsonEntity> getSubscriber = new GetSubscriber( suscriberEmail , null ); JsonEntity subscriberEntity = null ; try { subscriberEntity = getSubscriber.call(); } catch (Exception e) { logger.severe(e.getClass()+" : " + e.getMessage()); } if( subscriberEntity != null ){ logger.info("Retrieved suscriber object"); logger.info(subscriberEntity.getJsonObject().toString()); List<Object> seatSet = subscriberEntity.getJsonObject().getAsList("SeatSet"); int numEntitlements = entitlements.size(); int entitlementsAssigned = 0; for(SubscriptionEntitlement entitlement:entitlements){ JsonJavaObject seatJson = findSeat(seatSet, entitlement.getSubscriptionId()); if (seatJson != null) { String seatState = seatJson.getAsString("SeatState"); if(!seatState.equalsIgnoreCase("ASSIGNED")){ logger.info("seat not assigned !!!"); } else if(seatState.equalsIgnoreCase("ASSIGNED")){ entitlementsAssigned++; BSSProvisioning.getStateTransitionReport().get(this.subscriberEmail)[5] = new SimpleDateFormat(BSSProvisioning.DATE_FORMAT).format(new Date()); logger.info("seat assigned !!!"); if(numEntitlements == entitlementsAssigned){ BSSProvisioning.incrementSubscribersProvisioned(); seatsAssigned = true; } if(BSSProvisioning.getSubscribersQuantity().get() == (BSSProvisioning.getSubscribersProvisioned().get() + BSSProvisioning.getSubscribersFailed().get())){ if(BSSProvisioning.getSubscribersFailed().get()>0){ logger.warning("Provisioned Subscribers: "+ BSSProvisioning.getSubscribersProvisioned().get() +"\nFailed Subscribers: "+ BSSProvisioning.getSubscribersFailed().get()); }else{ logger.finest("ALL SUBSCRIBERS PROVISIONED !!!"); } BSSProvisioning.generateStateTransitionReport(); BSSProvisioning.generateSubscriberWeightReport(); BSSProvisioning.generateCallsCounterReport(); WeightManager.getInstance().shutdown(); } } } } }else{ logger.severe("the subscriber has not been retrieved !!!"); } } return seatsAssigned; } /** * This method checks for completion of task * @return <code>true</code> if complete, <code>false</code> otherwise */ private boolean checkForFinish(){ boolean completed = false; if(BSSProvisioning.getSubscribersQuantity().get() == (BSSProvisioning.getSubscribersProvisioned().get() + BSSProvisioning.getSubscribersFailed().get())){ completed = true; if(BSSProvisioning.getSubscribersFailed().get()>0){ logger.warning("Provisioned Subscribers: "+ BSSProvisioning.getSubscribersProvisioned().get() +"\nFailed Subscribers: "+ BSSProvisioning.getSubscribersFailed().get()); }else{ logger.finest("ALL SUBSCRIBERS PROVISIONED !!!"); } BSSProvisioning.generateStateTransitionReport(); BSSProvisioning.generateSubscriberWeightReport(); BSSProvisioning.generateCallsCounterReport(); WeightManager.getInstance().shutdown(); } return completed; } /** * This method will loop through a list of object representing the seats a subscriber owns, looking for the * one associated with the subscription specified as second argument * <p>. * @param seatSet a list of object representing the seats a subscriber owns<br> * @param subscriptionId the BSS subscription identifier<br> * @return a <code>JsonJavaObject</code> representing the seat associated with the subscription specified as second argument */ private JsonJavaObject findSeat(List<Object> seatSet, String subscriptionId) { for (Object seat : seatSet) { String nextSubscriptionId = "" + (long)((JsonJavaObject)seat).getAsDouble("SubscriptionId"); if (subscriptionId.equals(nextSubscriptionId)) { return (JsonJavaObject)seat; } } return null; } public JsonJavaObject getSubscriber() { return subscriber; } public String getSubscriberEmail() { return subscriberEmail; } public String getCustomerId() { return customerId; } public State getStatus() { return status; } public String getSubscriberId() { return subscriberId; } public String getSubscriberTaskKey() { return taskKey; } public void setSuppressEmail(boolean suppress){ suppressEmail = suppress; } }