Event Notifications

Event Notifications

The Oracle Primavera Cloud API provides real-time event notifications for changes to API objects. These notifications enable you to develop client Websocket applications that respond to specific events within the Primavera Cloud API. For example, you can create a client application that displays informative messages whenever a project object is created. Subscribe client applications to API entities using the wss://<host>:<port>/api/events endpoint, and use the Websocket protocol to receive Primavera Cloud API event notifications.

The Primavera Cloud API supports event notifications for actions and API entity objects. It categorizes events into three types:

  • CREATE: Indicates an object has been created.
  • UPDATE: Indicates a field of an object has been modified.
  • DELETE: Indicates an object has been deleted.

The API supports event notifications for the following objects:

Entity Object Comments
ApiEntityProject ApiEntityProjectBudget is subscribed automatically as part of entity subscription.
ApiEntityActivity  
ApiEntityProjectBudget  
ApiEntityBudgetItem  
ApiEntityBudgetChange  
ApiEntityBudgetTransaction  
ApiEntityBudgetTransfer  
ApiEntityWorkManagerTask  
ApiEntityWBS  
ApiEntityResource  
ApiEntityProjectSetting  
ApiEntityProjectIntegrationSource  
ApiEntityProjectAutoNumber  
ApiEntityProjectImage  
ApiEntityCapitalPortfolioMember  
ApiEntityPortfolio  
ApiEntityPortfolioImage  
ApiEntityPortfolioProgram  
ApiEntityPortfolioProject  
ApiEntityPortfolioMeasure  
ApiEntityPortfolioMeasureValue  
ApiEntityPortfolioMeasureManualValue  
ApiEntityResourceRoleAssignment  
ApiEntityResourceWorkRate  
ApiEntityResourceWorkRateCost  
ApiEntityResourceDemand  
ApiEntityResourceDemandCost  
ApiEntityResourceDemandData  
ApiEntityResourceDemandDataCost  
ApiEntityActivityFavorite  
ApiEntityActivityFinancial  
ApiEntityActivityUncertainty  
ApiEntityRelationship  
ApiEntityAssignment  
ApiEntityAssignmentFavorite  
ApiEntityAssignmentFinancial  
ApiEntityScheduleJob  
ApiEntityStrategyMeasure  
ApiEntityStrategyMeasureValue  
ApiEntityStrategyMeasureValueXReference  
ApiEntityStrategyProject  
ApiEntityBaseline  
ApiEntityUserBaseline  
ApiEntityProjectCashFlow  
ApiEntityProjectFinancial  
ApiEntityProjectActuals  
ApiEntityProjectActualsCost  
ApiEntityProjectActualsLineItem  
ApiEntityProjectActualsLineItemCost  
ApiEntityWorkspace  
ApiEntityProgramFilter  
ApiEntityProgramFinancial  
ApiEntityProgramPhaseFinancial  
ApiEntityProjectResourceWorkRateCost  
ApiEntityProjectResourceWorkRate  
ApiEntityCBSSheetCode  
ApiEntityCBSSheetRow  
ApiEntityCBSSheetRowCost  
ApiEntityCBSSheetSegment  
ApiEntityFund  
ApiEntityFundAllocationSpreadRow  
ApiEntityFundAllocationSpreadRowCost  
ApiEntityFundCost  
ApiEntityFundCostSpreadRow  
ApiEntityFundSpreadRow  
ApiEntityProgramBudget  
ApiEntityProgramBudgetItem  
ApiEntityProgramBudgetItemCost  
ApiEntityProgramBudgetStatusTransitionHistory  
ApiEntityProgramBudgetChange  
ApiEntityProgramBudgetChangeCost  
ApiEntityProgramBudgetChangeStatusTransitionHistory  
ApiEntityProgramBudgetTransfer  
ApiEntityProgramBudgetTransferStatusTransitionHistory  
ApiEntityProgramBudgetTransaction  
ApiEntityProgramBudgetTransactionCost  
ApiEntityFundPlanningScenario Filtering is not supported.
ApiEntityFundPlanProject Filtering is not supported.
ApiEntityPortfolioProjectFund Filtering is not supported.
ApiEntityFundPeriod Filtering is not supported.

Websocket Client

The Primavera Cloud API sends event notifications using the Websocket protocol, faciliating real-time communication between client and server applications. By leveraging Websockets, the Primavera Cloud API can promptly notify client applications of relevant events. Many programming languages, such as Java, either support the Websocket protocol or provide libraries for Websocket client implementation. To begin using Primavera Cloud API event notifications, create a client application that can receive Primavera Cloud API event notifications using Websockets. Refer to the Example Java Websocket Client section on this page to see an example of a client Websocket application.

Authentication

Primavera Cloud API eventing uses OAuth Token to authenticate Websocket clients. Websocket clients must provide a valid Primavera Cloud OAuth Token to connect to the API. Websocket requests to the https://<host>:<port>/api/events endpoint must include OAuth Token information in the form of a Bearer authentication header, similar to the other Primavera Cloud API endpoints.

Subscribing and Unsubscribing

To begin receiving Primavera Cloud API events, your Websocket client must subscribe to event notifications for specific actions or object types. Conversely, to stop receiving event notifications for an action or object, you need to send a subscription request with a subscription parameter set to false. Subscription requests, submitted in JSON format to the wss://<host>:<port>/api/events endpoint must contain the following parameters:

  • subscription (Boolean): Indicates whether to subscribe ( true) or unsubscribe ( false) the client to notifications related to the entityObjectType.
  • entityObjectType (String): The API object for which the event notifications are desired.
  • eventTypes (List of Strings): Types of events for which notifications will be sent - CREATE, UPDATE, and DELETE.
  • filters (String): Filter conditions of events for which notifications will be sent. Omit this parameter if no filtering is required.

  • For example, to receive event notifications for all event types for the API project object:
    {
        "subscription":true,
        "entityObjectType":"ApiEntityProject",
        "eventTypes":["CREATE","UPDATE","DELETE"]
    }
    
    
  • To receive event notifications for all event types for the API project object with a specific filter condition:
    {
        "subscription":true,
        "entityObjectType":"ApiEntityProject",
        "eventTypes":["CREATE","UPDATE","DELETE"],
        "filters":"projectCode=*construction"
    }
    
    
  • To stop receiving event notifications for all event types for the API project object:
    {
        "subscription":false,
        "entityObjectType":"ApiEntityProject",
        "eventTypes":["CREATE","UPDATE","DELETE"]
    }
    
    

Example Java Websocket Client

The following is an example of a Java Websocket client for consuming Primavera Cloud API event notifications. Ensure you have the JDK and Websocket third-party JARs to run the program.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
 
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
 
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
 
import jakarta.websocket.ClientEndpointConfig;
import jakarta.websocket.CloseReason;
import jakarta.websocket.ContainerProvider;
import jakarta.websocket.Endpoint;
import jakarta.websocket.EndpointConfig;
import jakarta.websocket.MessageHandler;
import jakarta.websocket.Session;
import jakarta.websocket.WebSocketContainer;
 
public class SampleWebsocketClient extends Endpoint {
 
    private static final Logger LOGGER = Logger.getLogger(SampleWebsocketClient.class.getName());
 
    private static final String USER_NAME = "<userName>";
    private static final String PASSWORD = "<password>";
    private static final String HOST_NAME = "<hostName>";
    private static final String OBJECT = "ApiEntityProject";
    private static final List<String> EVENTS = Arrays.asList("CREATE", "UPDATE", "DELETE");
    private static final String FILTERS = "projectName=*Construction";
    private static ObjectMapper mapper = new ObjectMapper();
    private Session session;
 
    public SampleWebsocketClient(JsonNode authTokenResponse) {
        try {
            URI endpointURI = new URI("wss://" + HOST_NAME + "/api/events");
            trustAllCert();
            WebSocketContainer container = ContainerProvider.getWebSocketContainer();
            container.connectToServer(this, getClientEndPointConfig(authTokenResponse), endpointURI);
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "Error in connecting to server:", e);
        }
    }
 
    public static void main(String[] args) {
        getSubscription();
    }
 
    private static void getSubscription() {
        try {
            trustAllCert();
            JsonNode authTokenResponse = getAuthTokenDetails();
            new SampleWebsocketClient(authTokenResponse).run();
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "Error in event subscription:", e);
        }
    }
 
    private static JsonNode getAuthTokenDetails() {
        HttpURLConnection conn = null;
        try {
            String tokenGenerationUrl = "https://" + HOST_NAME + "/primediscovery/apitoken/request?scope=" + "http://" + HOST_NAME + "/api";
            // Generating the OAuth Token
            URL tokenUrl = new URL(tokenGenerationUrl);
            conn = (HttpURLConnection) tokenUrl.openConnection();
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Accept", "application/json");
            conn.setRequestProperty("Content-Type", "application/json");
            String userCredentials = USER_NAME + ":" + PASSWORD;
            String base64Credentials = javax.xml.bind.DatatypeConverter.printBase64Binary(userCredentials.getBytes());
            String basicAuth = "Basic " + base64Credentials;
            conn.setRequestProperty("Authorization", basicAuth);
            if (conn.getResponseCode() < 200 || conn.getResponseCode() > 299) {
                throw new RuntimeException("Failed : HTTP error code : " + conn.getResponseCode() + "Error: " + readStreamData(conn.getErrorStream()));
            }
            JsonNode authTokenJson = new ObjectMapper().readValue(conn.getInputStream(), JsonNode.class);
            return authTokenJson;
        } catch (Exception e) {
            throw new RuntimeException("Failed to generate OAuth Token");
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
    }
 
    private static String readStreamData(InputStream is) throws IOException {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
            String output;
            StringBuilder buff = new StringBuilder();
            while ((output = br.readLine()) != null) {
                buff.append(output);
            }
            return buff.toString();
        }
    }
 
    private static void trustAllCert() throws NoSuchAlgorithmException, KeyManagementException {
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }
            @Override
            public void checkClientTrusted(X509Certificate[] certs, String authType) {
            }
            @Override
            public void checkServerTrusted(X509Certificate[] certs, String authType) {
            }
        } };
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        HostnameVerifier allHostsValid = new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
        HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
    }
 
    private ClientEndpointConfig getClientEndPointConfig(JsonNode authTokenResponse) {
        return ClientEndpointConfig.Builder.create().configurator(new ClientEndpointConfig.Configurator() {
            @Override
            public void beforeRequest(Map<String, List<String>> headers) {
                String tokenAuth = "Bearer " + authTokenResponse.get("accessToken").asText();
                headers.put("Authorization", Arrays.asList(new String[] { tokenAuth }));
                headers.put("x-prime-tenant-code", Arrays.asList(new String[] { authTokenResponse.get("primeTenantCode").asText() }));
                Iterator<Entry<String, JsonNode>> requestHeadersIterator = authTokenResponse.get("requestHeaders").fields();
                while (requestHeadersIterator.hasNext()) {
                    Entry<String, JsonNode> header = requestHeadersIterator.next();
                    headers.put(header.getKey(), Arrays.asList(new String[] { header.getValue().asText() }));
                }
                super.beforeRequest(headers);
            }
        }).build();
    }
 
    private void run() throws InterruptedException, IOException {
        String command = "";
        session.getAsyncRemote().sendText(getSubscriptionRequest());
        try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
            while (!command.equals("exit")) {
                command = br.readLine();
            }
        }
    }
 
 
    @Override
    public void onOpen(Session session, EndpointConfig endpoint) {
        LOGGER.info("Websocket session is open");
        this.session = session;
        session.addMessageHandler(new MessageHandler.Whole<String>() {
            @Override
            public void onMessage(String text) {
                try {
                    Object json = mapper.readValue(text, Object.class);
                    LOGGER.info("<-Message from Primavera Cloud is:");
                    LOGGER.info(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(json));
                } catch (Exception e) {
                    LOGGER.info("<-Message from Primavera Cloud is: " + text);
                }
            }
        });
    }
 
    @Override
    public void onClose(Session session, CloseReason closeReason) {
        session = null;
        System.exit(0);
    }
 
    private String getSubscriptionRequest() throws JsonProcessingException {
        return mapper.writeValueAsString(new SubscriptionRequest(Boolean.TRUE, OBJECT, EVENTS, FILTERS));
    }
}
 
class SubscriptionRequest {
 
    Boolean subscription;
    String entityObjectType;
    List<String> eventTypes;
    String filters;
 
    SubscriptionRequest(Boolean subscription, String entityObjectType, List<String> eventTypes, String filters) {
        this.subscription = subscription;
        this.entityObjectType = entityObjectType;
        this.eventTypes = eventTypes;
        this.filters = filters;
    }
 
    public String getEntityObjectType() {
        return entityObjectType;
    }
 
    public List<String> getEventTypes() {
        return eventTypes;
    }
 
    public String getFilters() {
        return filters;
    }
 
    public Boolean getSubscription() {
        return subscription;
    }
}

Notes:

  • Events are generated solely when fields directly stored within an object, its configured fields, or its code values undergo modifications. Modifications to fields referencing other object fields do not trigger events. For instance, altering the projectFinancial field on a project object does not prompt an update event for that project.
  • Objects deleted using cascade delete functionality (where referenced feature data is deleted) will not trigger a delete event. For example, initiating a permanent project delete from the Primavera Cloud application or from the API with cascadeOnDelete enabled will not return an event message.
  • If a code value associated with an object is deleted, an update event is triggered, though the deleted code values dp not appear in the object's updated fields.
  • When multiple object codes or flex fields are updated in a single request, each modified code value and flex field triggers an update event for the object.
  • Upon creating an object containing code and flex fields, only the object's create event is triggered. No update event for code and flex fields is triggered on object creation.
  • Deleting an object field containing a referenced value triggers the object's update event, with the deleted field value omitted from the object's updated fields. For example, deleting the projectFinancial field on a project object triggers an update event for the project object.
  • Operations (create, update, or delete) on an object impacting other objects within the Primavera Cloud instance result in notifications for the target object and all objects impacted by the operation.
  • A delete operation on subscription objects triggers a notification irrespective of the applied filters.