# Airship Java Library

Java library for using Airship's messaging platform and related features.
## Resources

- [Github Repo](https://github.com/urbanairship/java-library)
- [Javadocs](https://www.airship.com/docs/reference/libraries/java/latest/)
- [Airship API Reference](https://www.airship.com/docs/developer/rest-api/ua/)

## Installation

You can install the Java Library with Maven or manually.

### Maven

Add the library using Maven by adding the following lines to your pom.xml:

```xml
<!-- Airship Library Dependency-->
<dependency>
    <groupId>com.urbanairship</groupId>
    <artifactId>java-client</artifactId>
    <version>VERSION</version>
    <!-- Replace VERSION with the version you want to use -->
</dependency>
```


### Manual

To install the Java Library manually, clone the repository, run mvn package, and add the jar. The docs can also be built.

Clone the Java Library repo, and build the jar:

`mvn package`

Add the jar, replacing 'VERSION' with your desired version number located at a path similar to:

`target/java-client-VERSION.jar`

If you would like a copy of the javadocs, use:

`mvn javadoc:javadoc`

## Logging

Add log4j to your pom.xml:

**Add log4j to pom.xml**

```xml
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>VERSION</version>
</dependency>

<dependency>
  <groupId>log4j</groupId>
  <artifactId>log4j</artifactId>
  <version>VERSION</version>
</dependency>
```


Logging is done using the [Simple Logging Facade for Java](https://www.slf4j.org/). Using the logging facade allows for flexibility in logging choices. For example, to use log4j, you would add the following to your pom.xml:

**Add log4j as log handler**

```java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.log4j.BasicConfigurator;

Logger logger = LoggerFactory.getLogger("identifier");
// Use any configuration you need.
BasicConfigurator.configure();
logger.debug("Log all the things!");
```


Note the logging framework plus the adapter. For more info, see the [Simple Logging Facade documentation](https://www.slf4j.org/manual.html). Simply add the log handler of your choice.

## Getting Started

### Setting up an Airship Client

> The following is the minimum-viable UrbanAirshipClient configuration:

**Basic client configuration**

```java
UrbanAirshipClient client = UrbanAirshipClient.newBuilder()
    .setKey("your-app-key-here")
    .setSecret("your-app-secret-here")
    .build();
```


The UrbanAirshipClient handles the interaction between the client and the API. The client will throw an exception if there is an issue with the request, or if it is improperly configured.

### Connecting to European Server

> Connecting to European Server:

**Connect to European server**

```java
UrbanAirshipClient client = UrbanAirshipClient.newBuilder()
    .setKey("your-app-key-here")
    .setSecret("your-app-secret-here")
    .setUseEuropeanSite(true)
    .build();
```


### Create a Request

> Creating a request to the push API:

**Create push request**

```java
PushPayload payload = PushPayload.newBuilder()
    .setAudience(Selectors.iosChannel("ios_channel"))
    .setNotification(Notifications.alert("Here's a push!"))
    .setDeviceTypes(DeviceTypeData.of(DeviceType.IOS))
    .build();

PushRequest request = PushRequest.newRequest(payload);
```


Next, you are going to create a request.

### Send the Request

> Executing the push request:

**Execute push request**

```java
Response<PushResponse> response = null;
try {
    response = client.execute(request);
    logger.debug(String.format("Response %s", response.toString()));
} catch (IOException e) {
    logger.error("IOException in API request " + e.getMessage());
}
```


Once you have created a request, you pass it to be executed in the client created earlier.

## The Airship Client

The `UrbanAirshipClient` class handles requests to the Airship Engage API. This document covers the various configuration options, along with different methods for executing requests within the client. `UrbanAirshipClient` sets `AsyncRequestClient` as the underlying HTTP client by default for basic configurations. The `AsyncRequestClient` can be configured or a different HTTP client can be implemented by extending the `RequestCleint` interface.

### Configuration

> Minimum-viable UrbanAirshipClient config

**Basic client configuration**

```java
UrbanAirshipClient client = UrbanAirshipClient.newBuilder()
    .setKey("your-app-key-here")
    .setSecret("your-app-secret-here")
    .build();
```


As shown in the [Getting Started Guide](#getting-started), the minimum-viable UrbanAirshipClient client configuration requires an app key and a master secret.

In the following sections, we’ll explore some of the additional client configuration options available.

#### Client With Proxy

> Setting the client proxy:

**Configuring a proxy**

```java
Realm realm = new Realm.Builder("user", "password")
    .setScheme(Realm.AuthScheme.BASIC)
    .build();

ProxyServer proxyServer = new ProxyServer.Builder("test.urbanairship.com", 8080)
    .setRealm(realm)
    .build();

AsyncRequestClient asyncRequestClient = AsyncRequestClient.newBuilder()
    .setProxyServer(proxyServer)
    .build();

UrbanAirshipClient client = UrbanAirshipClient.newBuilder()
    .setKey("key")
    .setSecret("secret")
    .setClient(asyncRequestClient)
    .build();
```


Optionally, a client can be created with proxy server support.

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

#### Client With HTTP Parameter Settings

> Set HTTP parameters:

**Set HTTP parameters**

```java
DefaultAsyncHttpClientConfig.Builder clientConfigBuilder = new DefaultAsyncHttpClientConfig.Builder()
    .setConnectTimeout(20);

AsyncRequestClient asyncRequestClient = AsyncRequestClient.newBuilder()
    .setClientConfigBuilder(clientConfigBuilder)
    .build();

UrbanAirshipClient client = UrbanAirshipClient.newBuilder()
    .setKey("key")
    .setSecret("secret")
    .setClient(asyncRequestClient)
    .build();
```


A client can also be created with the option to set any of the HTTP parameters configurable through the [Async HTTP client](https://static.javadoc.io/org.asynchttpclient/async-http-client/2.0.38/org/asynchttpclient/DefaultAsyncHttpClientConfig.Builder.html), such as the protocol and connection parameters, by passing in a `DefaultAsyncHttpClientConfig.Builder`. In the example, the connection timeout is set to be 20 ms, thus overriding the default settings as infinite timeouts.

&nbsp;

#### Client with Custom Retry Logic

You may optionally specify a custom retry predicate. This predicate dictates how the client responds to failure, i.e., when should the client retry a failed request. The default retry predicate will retry all requests that return responses with status codes of 500 or higher, assuming they are not POST requests. We avoid retrying POST requests in order to prevent duplicates (e.g., retrying a push request may result in duplicate pushes). Requests are retried a maximum of 10 times, with an exponentially-increasing backoff period between each attempt.

> Configure the Predicate to retry:

**Configure retries**

```java
Predicate<FilterContext> retryPredicate = new Predicate<FilterContext>() {
    @Override
    public boolean apply(FilterContext input) {
        return input.getResponseStatus().getStatusCode() >= 500;
    }
};
```


In the example, we create a custom predicate that will retry all requests that return responses with status codes of 500 or greater. Unlike the default retry predicated, this predicate will retry POST requests.

&nbsp;

> Set the Predicate to retry:

**Set to retry**

```java
AsyncRequestClient asyncRequestClient = AsyncRequestClient.newBuilder()
    .setRetryPredicate(retryPredicate)
    .setMaxRetries(20)
    .build();

UrbanAirshipClient client = UrbanAirshipClient.newBuilder()
    .setKey("key")
    .setSecret("secret")
    .setClient(asyncRequestClient)
    .build();
```


We now configure an UrbanAirshipClient to use the above retryPredicate. We also increase the max number of retry attempts from 10 to 20.

### Executing Requests

> UrbanAirshipClient different modes of request execution:

**Different ways of executing requests**

```java
execute(Request<T> request)
execute(Request<T> request, ResponseCallback callback)
executeAsync(Request<T> request)
executeAsync(Request<T> request, ResponseCallback callback)
```


Once you have a client configured and some sort of request created, the UrbanAirshipClient class supports four different modes of request execution.

&nbsp;

#### Making Async Requests

> Initiate a non-blocking call:

**Initiate a non-blocking call**

```java
// Non-blocking request
Future<Response> futureResponse = client.executeAsync(request);

// Do other stuff...

// Retrieve your response after doing stuff.
Response<PushResponse> response = futureResponse.get();
```


Use the executeAsync(..) method to initiate a non-blocking call to the Airship API.

&nbsp;

#### Response Callbacks

> Setting ResponseCallback:

**Setting a Response Callback**

```java
ResponseCallback callback = new ResponseCallback() {
    @Override
    public void completed(Response response) {
        // Logic specifying what to do upon request completion.
        doSomething(response)
    }

    @Override
    public void error(Throwable throwable) {
        // Logic specifying what to do if the request fails.
        doSomethingElse(throwable)
    }
};
```


Both the execute and executeAsync methods accept an optional ResponseCallback argument. Below, we define a callback that executes the `doSomething(...)` function once a request completes, and the `doSomethingElse(...)` function if the request fails.

&nbsp;

&nbsp;

&nbsp;

> Example (executeAsync):

**Example of an executeAsync request**

```java
// Start the request execution. Once the request has completed (or thrown an error),
// the appropriate callback function will be triggered. ``executeAsync`` is non-blocking,
// so you can do other stuff while you wait for the callback to get triggered.
Future<Response> response = client.executeAsync(request, callback)

// Do other stuff...
```


We can use this callback with either execute or executeAsync:

> Example (execute):

**Example of an execute request**

```java
// Start the request execution . Once the request has completed (or thrown an error),
// the appropriate callback function will be triggered. ``execute`` is blocking, so
// you must wait for the request to complete (or fail), after which the callback is triggered
// and the Response<..> is returned.
Response<PushResponse> response = client.execute(request, callback)
```


&nbsp;

&nbsp;

&nbsp;

&nbsp;

#### Exceptions

> Handling exceptions in the ResponseCallback:

**ResponseCallback exception handling**

```java
ResponseCallback callback = new ResponseCallback() {
    @Override
    public void completed(Response response) {
        // Logic specifying what to do upon request completion.
        doSomething(response)
    }

    @Override
    public void error(Throwable throwable) {
        if (throwable instanceof ClientException) {
            // Handle a 4xx response
        } else if (throwable instance of ServerException)
            // Handle a 5xx response
        } else {
            // Handle any other failure
        }
    }
};
```


The client will throw different exceptions depending on mode of execution. If you are not using a callback, all exceptions present as RuntimeExceptions. If you choose to use a callback, you can customize the error method to distinguish between ClientExceptions (4xx responses), ServerExceptions (5xx responses), and any other potential failures.

## API Endpoint examples

### Push

The PushPayload has three components:

* Audience and Selectors
* Notifications
* Device Types

The first is the Audience. The audience is composed of Selectors, which can be compound or atomic (not compound). Selectors provide logical combinations of AND, OR, and NOT.

#### Audience and Selectors

> Send to your audience with kittens tag:

**Send to a tag**

```java
Selectors.tag("kittens");
```


The Selectors and DeviceType classes provide factory methods that can be used together to create an Audience Selector.

> A more complex audience query:

**Complex audience example**

```java
Selector andSelector = Selectors.and(Selectors.tag("puppies"), Selectors.tag("kittens"));
Selector notSelector = Selectors.not(Selectors.tag("fish"));
Selector compound = Selectors.or(andSelector, notSelector);
```


More complex logic is possible.

#### Notifications

> An example of an iOS notification implementing expiry and interactive notifications:

**Example send to iOS with expiry and interactive features**

```java
PushExpiry expiry = PushExpiry.newBuilder()
    .setExpirySeconds(3600)
    .build();

Interactive interactive = Interactive.newBuilder()
    .setType("ua_yes_no_foreground")
    .setButtonActions(ImmutableMap.of(
        "yes",
        Actions.newBuilder()
            .addTags(new AddTagAction(TagActionData.single("tag1")))
            .build(),
        "no",
        Actions.newBuilder()
            .addTags(new AddTagAction(TagActionData.single("tag2")))
            .build()))
    .build();

IOSDevicePayload iosPayload = IOSDevicePayload.newBuilder()
    .setAlert("alert")
    .setExpiry(expiry)
    .setInteractive(interactive)
    .build();

PushPayload payload = PushPayload.newBuilder()
    .setAudience(Selectors.iosChannel("9c36e8c7-5a73-47c0-9716-99fd3d4197d5"))
    .setNotification(Notifications.notification(iosPayload))
    .setDeviceTypes(DeviceTypeData.of(DeviceType.IOS))
    .build();
```


Notifications are the second part of the PushPayload. Notifications are configured for each type of device you would like to send a message to. A Notification for an iOS device contains options for alert, badge, sound, content_available, extra, expiry, priority, category, interactive, etc. Other platforms, e.g., Android, may offer different configurations based on available features.

> An example of an iOS notification implementing global attributes personalization:

**Global attributes example**

```java
ArrayList productsList = new ArrayList();
​
HashMap mMap = new HashMap();
mMap.put("id", 1);
mMap.put("name", "New Line Sneakers");
mMap.put("price","79.95");
productsList.add(mMap);
​
mMap = new HashMap();
mMap.put("id", 2);
mMap.put("name","Old Line Sneakers");
mMap.put("price","59.95");
productsList.add(mMap);
​
IOSDevicePayload iosPayload = IOSDevicePayload.newBuilder()
        .setAlert("Hi from Airship!{{#if super_sale }} We're having a sale on {{ products.0.name }}!{{/if}}")
        .addExtraEntry("url","http://www.urbanairship.com")
        .build();
​
PushOptions myOptions = PushOptions.newBuilder()
        .setPersonalization(true)
        .build();
​
PushPayload payload = PushPayload.newBuilder()
        .setAudience(Selectors.or(Selectors.iosChannel("9c36e8c7-5a73-47c0-9716-99fd3d4197d5"), Selectors.tags("sports","entertainment")))
        .setNotification(Notifications.notification(iosPayload))
        .setDeviceTypes(DeviceTypeData.of(DeviceType.IOS))
        .addGlobalAttributes("products", productsList)
        .addGlobalAttributes("super_sale",true)
        .setPushOptions(myOptions)
        .build();
```


> An example of an iOS notification implementing media attachment and some specific iOS options:

**Media attachment and special iOS options example**

```java
IOSAlertData iosAlert = IOSAlertData.newBuilder()
        .setTitle("Kevin Gausman Throws a Perfect Game")
        .setBody("Kevin Gausman stymies the Houston Astros for San Francisco's second perfect game in franchise history.")
        .setSummaryArg("San Francisco Giants")
        .setSummaryArgCount(1)
        .build();

IOSMediaContent iosMediaContent = IOSMediaContent.newBuilder()
        .setBody("Kevin Gausman")
        .setBody("Gausman strikes out Justin Turner")
        .build();

IOSMediaOptions iosMediaOptions = IOSMediaOptions.newBuilder()
        .setCrop(Crop.newBuilder()
                .setHeight(BigDecimal.valueOf(0.5))
                .setWidth(BigDecimal.valueOf(0.5))
                .setX(BigDecimal.valueOf(0.25))
                .setY(BigDecimal.valueOf(0.25))
                .build())
        .setTime(15)
        .build();

MediaAttachment mediaAttachment = MediaAttachment.newBuilder()
        .setContent(iosMediaContent)
        .setOptions(iosMediaOptions)
        .setUrl("https://media.example.com/media/6nJmrhlu4aL1m/giphy.gif")
        .build();

IOSDevicePayload iOSPayload = IOSDevicePayload.newBuilder()
        .setThreadId("sfGiants_news")
        .setAlert(iosAlert)
        .setRelevanceScore(1.0)
        .setIosInterruptionLevel(IOSInterruptionLevel.PASSIVE)
        .setSoundData(IOSSoundData.newBuilder().setName("strike-call").build())
        .setMediaAttachment(mediaAttachment)
        .setMutableContent(true)
        .build();

PushPayload pushPayload = PushPayload.newBuilder()
        .setAudience(Selectors.all())
        .setDeviceTypes(DeviceTypeData.of(DeviceType.IOS))
        .setNotification(Notifications.notification(iOSPayload))
        .build();
```




&nbsp;

&nbsp;

&nbsp;

&nbsp;

#### Device Types

> Here’s an example of setting the device types to iOS and Android:

**Setting device types to target**

```java
DeviceTypeData deviceTypeData  = DeviceTypeData.of(DeviceType.IOS, DeviceType.ANDROID);
```


The final part of the PushPayload is DeviceTypes, which defines the platform you’re sending to, e.g., iOS or Android. Messages can be segregated by device types. Set the device types you want to send to using a DeviceTypeData object.

### Schedules

#### Create a Scheduled Notification

> Scheduling a push notification:

**Schedule a push**

```java
SchedulePayload schedulePayload = SchedulePayload.newBuilder()
        .setName("Morning People")
        .setSchedule(Schedule.newBuilder()
                .setScheduledTimestamp(DateTime.parse("2020-06-03T09:15:00Z"))
                .build())
        .setPushPayload(PushPayload.newBuilder()
                .setDeviceTypes(DeviceTypeData.of(DeviceType.IOS, DeviceType.ANDROID))
                .setNotification(Notifications.alert("Good Day Sunshine"))
                .setAudience(Selectors.tag("earlyBirds"))
                .build())
        .build();

ScheduleRequest scheduleRequest = ScheduleRequest.newRequest(schedulePayload);
Response<ScheduleResponse> response = client.execute(scheduleRequest);
```


You can use the `ScheduleRequest.newRequest(<schedule_payload>)` method to create a scheduled notification.
&nbsp;

&nbsp;

&nbsp;

#### Update a Schedule

> Updating a scheduled notification:

**Update a scheduled push**

```java
SchedulePayload schedulePayload = SchedulePayload.newBuilder()
        .setName("I would like to subscribe to your newsletter")
        .setSchedule(Schedule.newBuilder()
                .setScheduledTimestamp(DateTime.parse("2020-04-01T18:45:00Z"))
                .build())
        .setPushPayload(PushPayload.newBuilder()
                .setDeviceTypes(DeviceTypeData.of(DeviceType.IOS, DeviceType.ANDROID))
                .setNotification(Notifications.alert("Check your inbox!"))
                .setAudience(Selectors.tag("intriguing"))
                .build())
        .build();

ScheduleRequest scheduleRequest = ScheduleRequest.newUpdateRequest(schedulePayload, "5cde3564-ead8-9743-63af-821e12337812");
Response<ScheduleResponse> response = client.execute(scheduleRequest);
```



You can use the `ScheduleRequest.newUpdateRequest(<schedule_payload> "<schedule_id>")` method to update a scheduled notification.
&nbsp;

&nbsp;

&nbsp;

#### Lookup Schedule

> Looking up a scheduled notification:

**Look up a scheduled push**

```java
ScheduleListingRequest request = ScheduleListingRequest.newRequest("5cde3564-ead8-9743-63af-821e12337812");
Response<ListAllSchedulesResponse> response = client.execute(request);
SchedulePayloadResponse schedule = response.getBody().get().getSchedules().get(0);

// Get the schedule's name
Optional<String> name = schedule.getName();
// Get the push IDs
Set<String> pushIds = schedule.getPushIds();
// Get the scheduled time
Schedule sched = schedule.getSchedule();
// Get the associated push payload
PushPayload payload = schedule.getPushPayload();
// Get the URL
Optional<String> url = schedule.getUrl();
```


To lookup a schedule, use the `ScheduleListingRequest.newRequest("<schedule_id>")` method.

&nbsp;

&nbsp;

&nbsp;

#### List Schedules

> List all scheduled notifications:

**List scheduled pushes**

```java
ScheduleListingRequest request = ScheduleListingRequest.newRequest();
Response<ListAllSchedulesResponse> response = client.execute(request);

// Get the list of schedules
List<SchedulePayloadResponse> schedules = response.getBody().get().getSchedules();
```


To view a list of all created schedules, use the `ScheduleListingRequest.newRequest()` method.

#### Delete Schedule

> Delete schedule request:

**Delete a scheduled push**

```java
ScheduleDeleteRequest request = ScheduleDeleteRequest.newRequest("schedule_1234");
Response<GenericResponse> response = client.execute(request);
```


To delete a schedule, use the `ScheduleDeleteRequest.newRequest("<schedule_id>")` method.

### A/B Tests

#### Create A/B Tests

> Create an A/B Test with a schedule time:

**Schedule an A/B test**

```java
Schedule schedule = Schedule.newBuilder()
                .setScheduledTimestamp(DateTime.now().plusDays(1))
                .build();

Variant variantOne = Variant.newBuilder()
        .setPushPayload(VariantPushPayload.newBuilder()
                .setNotification(Notification.newBuilder()
                        .setAlert("Hello there!")
                        .build()
                )
                .build())
        .setSchedule(schedule)
        .build();

Variant variantTwo = Variant.newBuilder()
        .setPushPayload(VariantPushPayload.newBuilder()
                .setNotification(Notification.newBuilder()
                        .setAlert("Boogaloo")
                        .build()
                )
                .build())
        .setSchedule(schedule)
        .build();

Experiment experiment = Experiment.newBuilder()
        .setName("Another test")
        .setDescription("Its a test!")
        .setDeviceTypes(DeviceTypeData.of(DeviceType.IOS))
        .setAudience(Selectors.namedUser("NamedUserID"))
        .addVariant(variantOne)
        .addVariant(variantTwo)
        .build();

ExperimentRequest request = ExperimentRequest.newRequest(experiment);
Response<ExperimentResponse> response = client.execute(request);
```


To create an A/B Test, use the `ExperimentRequest.newRequest()` method.

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

#### Delete A/B Tests

> Delete a scheduled A/B Test:

**Delete a scheduled A/B test**

```java
ExperimentDeleteRequest experimentDeleteRequest = ExperimentDeleteRequest.newRequest("experimentId");
Response<ExperimentResponse> response = client.execute(experimentDeleteRequest);
```


To delete a scheduled A/B Test, use the `ExperimentDeleteRequest.newRequest()` method.

Note that experiments can only be deleted before they start.

### Personalization

#### Create Template

> Create a template:

**Create a template**

```java
TemplateVariable titleVariable = TemplateVariable.newBuilder()
        .setKey("TITLE")
        .setName("Title")
        .setDescription("e.g. Mr, Ms, Dr, etc")
        .setDefaultValue("")
        .build();

TemplateVariable firstNameVariable = TemplateVariable.newBuilder()
        .setKey("FIRST_NAME")
        .setName("First Name")
        .setDescription("Given name")
        .setDefaultValue(null)
        .build();

TemplateVariable lastNameVariable = TemplateVariable.newBuilder()
        .setKey("LAST_NAME")
        .setName("Last Name")
        .setDescription("Family name")
        .setDefaultValue("")
        .build();

PartialPushPayload partialPushPayload = PartialPushPayload.newBuilder()
        .setNotification(Notification.newBuilder()
                .setAlert("Hello {{TITLE}} {{FIRST_NAME}} {{LAST_NAME}}, welcome to our loyalty program!")
                .build()
        )
        .build();

TemplateRequest request = TemplateRequest.newRequest()
        .setName("Template Name")
        .setDescription("A description")
        .addVariable(titleVariable)
        .addVariable(firstNameVariable)
        .addVariable(lastNameVariable)
        .setPush(partialPushPayload);

Response<TemplateResponse> response = client.execute(request);
```


To create a template, use the `TemplateRequest.newRequest()` method.

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

#### Update Template

> Update an existing template:

**Update a template**

```java
PartialPushPayload partialPushPayload = PartialPushPayload.newBuilder()
        .setNotification(Notification.newBuilder()
                .setAlert("Hello {{FIRST_NAME}} {{LAST_NAME}}, this is a test!")
                .build()
        )
        .build();

TemplateRequest request = TemplateRequest.newRequest(UUID.randomUUID().toString())
        .setName("your-template-name")
        .setPush(partialPushPayload);

Response<TemplateResponse> response = client.execute(request);
```


To update a template, use the `TemplateRequest.newRequest(<template-id>)` method.

&nbsp;

&nbsp;

&nbsp;

#### Push to Template

> In the example below, we push to the template we created in the Create Template section:

**Send a push using a template**

```java
TemplatePushPayload payload = TemplatePushPayload.newBuilder()
        .setAudience(Selectors.namedUser("named-user"))
        .setDeviceTypes(DeviceTypeData.of(DeviceType.ANDROID))
        .setMergeData(TemplateSelector.newBuilder()
                .setTemplateId("template-id-123")
                .addSubstitution("FIRST_NAME", "James")
                .addSubstitution("LAST_NAME", "Brown")
                .addSubstitution("TITLE", "Mr.")
                .build())
        .build();

TemplatePushRequest request = TemplatePushRequest.newRequest()
        .addTemplatePushPayload(payload);

Response<TemplateResponse> response = client.execute(request);
```


To push to a template, use the `TemplatePushRequest.newRequest()` method.

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

#### Template Lookup

> Template lookup:

**Look up a template**

```java
TemplateListingRequest request = TemplateListingRequest.newRequest("template-id");
Response<TemplateListingResponse> response = client.execute(request);
```


To look up a template, use the `TemplateListingRequest.newRequest("template-id")` method.

#### Template Listing

> List all Templates:

**List templates**

```java
TemplateListingRequest request = TemplateListingRequest.newRequest();
Response<TemplateListingResponse> response = client.execute(request);
```


To list all templates, use the `TemplateListingRequest.newRequest()` method:

#### Delete Template

> Delete a Template:

**Delete a template**

```java
TemplateDeleteRequest request = TemplateDeleteRequest.newRequest("template-id");
Response<TemplateResponse> response = client.execute(request);
```


To delete a template, use the `TemplateDeleteRequest.newRequest("template-id")` method.

## Audience Management

### Channels

#### Lookup Channel

> Look up an individual channel:

**Look up a channel**

```java
ChannelRequest request = ChannelRequest.newRequest("channel_id_123");
Response<ChannelResponse> response = client.execute(request);
ChannelView channel = response.getBody().get().getChannelView().get();

// The channel ID
String channelId = channel.getChannelId();
// The channel type -- one of IOS, ANDROID, or ADM
String channelType = channel.getChannelType();
// Whether the channel is installed or not
boolean installed = channel.isInstalled();
// Whether the channel is opted in to push or not
boolean optedIn = channel.isOptIn();
// Whether background push is enabled on the device
Optional<Boolean> background = channel.getBackground();
// The push address associated with the channel
Optional<String> pushAddress = channel.getPushAddress();
// When the channel was created
DateTime created = channel.getCreated();
// The date at which the channel was last registered
DateTime lastRegistration = channel.getLastRegistration();
// The alias (potentially) associated with the channel
Optional<String> alias = channel.getAlias();
// The tags associated with the channel
ImmutableSet<String> tags = channel.getTags();
// The tag groups associated with the channel
ImmutableMap<String, ImmutableSet<String>> tagGroups = channel.getTagGroups();
// An IosSettings object
Optional<IosSettings> iosSettings = channel.getIosSettings();
```


To lookup a specific channel, use the `ChannelRequest.newRequest("<channel_id>")` method.

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

#### List Channels

> List all channels:

**List channels**

```java
ChannelRequest request = ChannelRequest.newRequest();
Response<ChannelResponse> response = client.execute(request);
ChannelView channels = response.getBody().get().getChannelView().get();
```


To list all channels, use the `ChannelRequest.newRequest()` method.

### Named Users

#### Associate Named User

> Associate channel to named user:

**Associate a channel to a named user**

```java
NamedUserRequest request = NamedUserRequest.newAssociationRequest()
        .setChannel("ee4b5101-164c-485c-ad91-68b1d3d753cc", ChannelType.IOS)
        .setNamedUserId("id-1234");

Response<GenericResponse> response = client.execute(request);
```


To associate channels to a Named User, use the NamedUserRequest class.

&nbsp;

#### Disassociate Named User

> Disassociate channel from named user:

**Disassociate a channel from a named user**

```java
NamedUserRequest request = NamedUserRequest.newDisassociationRequest()
        .setChannel("0ab7d6f0-0f61-4963-afe0-5ef53735b00d", ChannelType.ANDROID);

Response<GenericResponse> response = client.execute(request);
```


To disassociate channels from a Named User, use the NamedUserRequest class.

#### Look up a Named User

> Look up a named user:

**Look up a named user**

```java
NamedUserListingRequest request = NamedUserListingRequest.newRequest("id-1234");
Response<NamedUserListingResponse> response = client.execute(request);
NamedUserView namedUser = response.getBody().get().getNamedUserView().get();

// The named user ID
String namedUserId = namedUser.getNamedUserId();
// Map of tag groups and the associated sets of tags
ImmutableMap<String, ImmutableSet<String>> namedUserTags = namedUser.getNamedUserTags();
// All channel objects associated with the named user
ImmutableSet<ChannelView> channelViews = namedUser.getChannelViews();
```


To lookup a named user, use the `NamedUserListRequest.newRequest("<named_user_id>")` method.

&nbsp;

&nbsp;

#### List Named Users

> List named users:

**List named users**

```java
NamedUserListingRequest request = NamedUserListingRequest.newRequest();
Response<NamedUserListingResponse> response = client.execute(request);
ImmutableList<NamedUserView> namedUsers = response.getBody().get().getNamedUserViews().get();
```


To list named users, use the `NamedUserListRequest.newRequest()` method.

### Tags
See: [Tags: Named Users](#add-remove-tags-from-named-users)

#### Add/Remove Tags From Channel

> Channel Tags:

**Set up a channel tag request**

```java
Set<String> tags = new HashSet<String>();
tags.add("loyalty");
tags.add("platinum");
tags.add("sports");

Set<String> remove = new HashSet<String>();
tags.add("gold");
tags.add("news");

ChannelTagRequest request = ChannelTagRequest.newRequest()
    .addIOSChannels("56071f7c-921f-4981-9568-b5f7cef427cd", "a74897b2-3ff3-4741-8b69-1d739fc3830f")
    .addAndroidChannel("ecf68576-c7ac-48cc-9aaa-94b63e6dccda")
    .addTags("device", tags)
    .removeTags("device", remove);

Response<GenericResponse> response = client.execute(request);
```


To add tags use the ChannelTagRequest class. In the following example, we add the tags loyalty, platinum, and sports, and remove the tags gold and news.

&nbsp;

&nbsp;

&nbsp;

&nbsp;

#### Add/Remove Tags From Named Users

To execute tag operations on a named user, use the NamedUserTagRequest class.

> Add Tags:

**Add tags**

```java
Set<String> tags = new HashSet<String>();
tags.add("loyalty");
tags.add("platinum");
tags.add("sports");

NamedUserTagRequest request = NamedUserTagRequest.newRequest()
        .addNamedUsers("user-1", "user-2", "user-3")
        .addTags("device", tags);
Response<GenericResponse> response = client.execute(request);
```


The `addTags("<tag_group>", <tag_set>)` method is used for adding tags.

&nbsp;

&nbsp;

&nbsp;

> Remove Tags:

**Remove tags**

```java
Set<String> tags = new HashSet<String>();
tags.add("loyalty");
tags.add("platinum");
tags.add("sports");

NamedUserTagRequest request = NamedUserTagRequest.newRequest()
        .addNamedUsers("user-1", "user-2", "user-3")
        .removeTags("device", tags);
Response<GenericResponse> response = client.execute(request);
```


The `removeTags("<tag_group>", <tag_set>)` method is used for removing tags.

&nbsp;

&nbsp;

&nbsp;

> Set Tags:

**Set tags**

```java
Set<String> tags = new HashSet<String>();
tags.add("loyalty");
tags.add("platinum");
tags.add("sports");

NamedUserTagRequest request = NamedUserTagRequest.newRequest()
        .addNamedUsers("user-1", "user-2", "user-3")
        .setTags("device", tags);
Response<GenericResponse> response = client.execute(request);
```


The `setTags("<tag_group>", <tag_set>)` method is used to wipe the current set of tags on the device with the provided set.

### Segments

#### Create Segment

> Create a Segment:

**Create a segment**

```java
SegmentRequest request = SegmentRequest.newRequest();

// Define the segment criteria
Selector andSelector = Selectors.tags("java", "lib");
Selector compound = Selectors.or(andSelector, Selectors.not(Selectors.tag("mfd")));
DateRange dateRange = Selectors.weeks(3);
Selector location = Selectors.location("us_zip", "97214", dateRange);
Selector locationCriteria = Selectors.or(compound, location);

// Set the request criteria and display name, and execute the request.
request.setCriteria(locationCriteria);
request.setDisplayName("UAJavaLib");
Response<GenericResponse> response = client.execute(request);
```


To create a segment, use the `SegmentRequest.newRequest()` method.

&nbsp;

&nbsp;

&nbsp;

#### Look up a Segment

> Look up a Segment:

**Look up a segment**

```java
SegmentLookupRequest request = SegmentLookupRequest.newRequest("<segment_id>");
Response<SegmentView> response = client.execute(request);

// Get the segment criteria
Selector criteria = response.getBody().get().getCriteria();
// Get the segment display name
String displayName = response.getBody().get().getDisplayName();
```


To get information on a particular segment, use the `SegmentLookupRequest.newRequest("<segment_id>")` method.

&nbsp;

#### List Segments

> List all Segments:

**List segments**

```java
SegmentListingRequest request = SegmentListingRequest.newRequest();
Response<SegmentListingResponse> response = client.execute(request);

// Get the first segment in the list
SegmentListingView segment = response.getBody().get().getSegmentListingViews().get(0);

// Get the segment display name
String displayName = segment.getDisplayName();

// Get the segment ID
String id = segment.getSegmentId();
```


To get a list of all segments, use the `SegmentListingRequest.newRequest()` method.

&nbsp;

&nbsp;

&nbsp;

#### Update Segment

> Update a segment:

**Update a segment**

```java
SegmentRequest request = SegmentRequest.newUpdateRequest("<segment_id>");

// Define the segment criteria
Selector andSelector = Selectors.tags("java", "lib");
Selector compound = Selectors.or(andSelector, Selectors.not(Selectors.tag("mfd")));
DateRange dateRange = Selectors.weeks(3);
Selector location = Selectors.location("us_zip", "97214", dateRange);
Selector locationCriteria = Selectors.or(compound, location);

// Set the request criteria and display name, and execute the request.
request.setCriteria(locationCriteria);
request.setDisplayName("UAJavaLib");
Response<GenericResponse> response = client.execute(request);
```


To update a segment, use the `SegmentRequest.newUpdateRequest("<segment_id>")` method.

&nbsp;

&nbsp;

#### Delete Segment

> Delete a segment:

**Delete a segment**

```java
SegmentDeleteRequest request = SegmentDeleteRequest.newRequest("<segment_id>");
Response<GenericResponse> response = client.execute(request);
```


To delete a segment, use the `SegmentDeleteRequest.newRequest("<segment_id>")` method.

### Static Lists

#### Create Static List

> Create a static list:

**Create a static list**

```java
StaticListRequest request = StaticListRequest.newRequest("platinum_members")
        .setDescription("Subscribers with platinum status.")
        .addExtra("cool", "extras")
        .addExtra("another", "extra");

Response<GenericResponse> response = client.execute(request);
```


To create a static list, use the `StaticListRequest.newRequest("<list_name>")` method.

#### Upload Static List

> Upload a static list:

**Upload a static list**

```java
File dataDirectory = new File("src/data");
String filePath = dataDirectory.getAbsolutePath() + "/platinum.csv";
StaticListUploadRequest request = StaticListUploadRequest.newRequest("platinum_members", filePath);
Response<GenericResponse> response = client.execute(request);
```


To upload a static list, use the `StaticListUploadRequest.newRequest("<list_name>", "<file_path>")` method.

#### Download Static List

> Download the CSV associated with a static list:

**Download static list CSV**

```java
StaticListDownloadRequest request = StaticListDownloadRequest.newRequest("<list_name>");
Response<String> response = client.execute(request);
```


To download the CSV associated with a static list, use the `StaticListDownloadRequest.newRequest("<list_name>")` method.

&nbsp;

> Direct the output to FileOutputStream:

**Direct static list CSV download to FileOutputStream**

```java
FileOutputStream fileOutputStream = new FileOutputStream(new File("list.csv"));

StaticListDownloadRequest request = StaticListDownloadRequest.newRequest("<list_name>")
    .setOutputStream(fileOutputStream);
Response<String> response = client.execute(request);
```


Optionally, you can direct the output to a FileOutputStream by using the setResponseFile setter.

&nbsp;

&nbsp;

> Download a Lifecycle list:

**Download a lifecycle list**

```java
StaticListDownloadRequest request = StaticListDownloadRequest.newRequest(LifecycleListType.UNINSTALLS_LAST_MONTH)
    .setOutputStream(fileOutputStream);
Response<String> response = client.execute(request);
```


You can also call the `StaticListDownloadRequest.newRequest()` method with one of the Lifecycle List types defined in the LifecycleListType enum.

## Reports

### Platform Statistics

Optionally, you can direct the output to a FileOutputStream by using the setResponseFile setter.

> Platform Statistic Reports:

**Getting platform statistic reports**

```java
DateTime start = new DateTime(2015, 10, 1, 12, 0, 0, 0);
DateTime end = start.plus(Period.hours(48));

// App Opens Report
PlatformStatsRequest appOpensRequest = PlatformStatsRequest.newRequest(PlatformStatsRequestType.APP_OPENS)
    .setStart(start)
    .setEnd(end)
    .setPrecision(Precision.HOURLY);

// Time in App Report
PlatformStatsRequest tiaRequest = PlatformStatsRequest.newRequest(PlatformStatsRequestType.TIME_IN_APP)
    .setStart(start)
    .setEnd(end)
    .setPrecision(Precision.HOURLY);

// Opt-ins Report
PlatformStatsRequest optInsRequest = PlatformStatsRequest.newRequest(PlatformStatsRequestType.OPT_INS)
    .setStart(start)
    .setEnd(end)
    .setPrecision(Precision.HOURLY);

// Opt-outs Report
PlatformStatsRequest optOutsRequest = PlatformStatsRequest.newRequest(PlatformStatsRequestType.OPT_OUTS)
    .setStart(start)
    .setEnd(end)
    .setPrecision(Precision.HOURLY);

// Push Report
PlatformStatsRequest pushSendsRequest = PlatformStatsRequest.newRequest(PlatformStatsRequestType.SENDS)
    .setStart(start)
    .setEnd(end)
    .setPrecision(Precision.HOURLY);

Response<PlatformStatsResponse> appOpensResponse = client.execute(appOpensRequest);
Response<PlatformStatsResponse> tiaResponse = client.execute(tiaRequest);
Response<PlatformStatsResponse> optInsResponse = client.execute(optInsRequest);
Response<PlatformStatsResponse> optOutsResponse = client.execute(optOutsRequest);
Response<PlatformStatsResponse> pushSendsResponse = client.execute(pushSendsRequest);

PlatformStats stats = appOpensResponse.getBody().get().getPlatformStatsObjects().get().get(0);
// Get the number of iOS devices
int ios = stats.getIos();
// Get the number of Android devices
int android = stats.getAndroid();
// Get the time interval
DateTime date = stats.getDate();
```


The various reports that provide platform feedback are all handled by the PlatformStatsRequest class. This group of reports includes the App Opens Report, Time in App Report, Opt-ins Report, Opt-outs Report, and Push Reports. Each of the following requests requires a start date, end date, and precision.

### Individual Push Response Statistics

> Individual Push Response Statistics request:

**Get the push response statistics report**

```java
PushInfoRequest request = PushInfoRequest.newRequest("ca15a452-ad5d-4bd9-95bb-e190eeba32cd");
Response<PushInfoResponse> response = client.execute(request);
PushInfoResponse pushInfo = response.getBody().get();

// Number of sends
int sends = pushInfo.getSends();
// Number of direct responses to the push
int directResponses = pushInfo.getDirectResponses();
// When the push was sent
DateTime date = pushInfo.getPushTime();
// The push type -- can be one of BROADCAST_PUSH, SCHEDULED_PUSH, TAG_PUSH, UNICAST_PUSH
PushType type = pushInfo.getPushType();
// The unique identifier for the push
UUID pushId = pushInfo.getPushId();
// The (optional) group ID
Optional<UUID> groupId = pushInfo.getGroupID();
```


Use the `PushInfoRequest.newRequst("<push_id>")` class to get information on a particular push id.

### Response Listing

> Response Listing request:

**Get the response listing report**

```java
DateTime start = new DateTime(2015, 10, 1, 12, 0, 0, 0);
DateTime end = start.plus(Period.hours(48));

PushListingRequest request = PushListingRequest.newRequest()
    .setStart(start)
    .setEnd(end)
    .setLimit(20);

Response<PushListingResponse> response = client.execute(request);

// Get the first item in an array of push info responses. You can use all of the getters
// listed in the "Individual Push Response Statistics" section.
PushInfoResponse pushInfo = response.getBody().get().getPushInfoList().get().get(0);
```


The PushListingRequest class is used to make requests to the /api/reports/responses/list endpoint.

## Custom Events

> Setting your bearer token:

**Set up a bearer token**

```java
UrbanAirshipClient client = UrbanAirshipClient.newBuilder()
    .setKey("your-app-key-here")
    .setSecret("your-app-secret-here")
    .setBearerToken("your-bearer-token-here")
    .build();
```


You must set a bearer token on the UrbanAirshipClient for custom events requests.
If only custom event requests are being made, the secret is optional.

> Create a custom event:

**Create a custom event**

```java
// Airship channel identifier for the user who triggered the event.
CustomEventUser customEventUser = CustomEventUser.newBuilder()
                .setCustomEventChannelType(CustomEventChannelType.ANDROID_CHANNEL)
                .setChannel("e393d28e-23b2-4a22-9ace-dc539a5b07a8")
                .build();

// The body object which describes the user action.
CustomEventBody customEventBody = CustomEventBody.newBuilder()
        .setName("purchased")
        .setValue(new BigDecimal(120.49))
        .setTransaction("886f53d4-3e0f-46d7-930e-c2792dac6e0a")
        .setInteractionId("your.store/us/en_us/pd/shoe/pid-11046546/pgid-10978234")
        .setInteractionType("url")
        .setSessionId("22404b07-3f8f-4e42-a4ff-a996c18fa9f1")
        .build();

// The date and time when the event occurred.
DateTime occurred = new DateTime(2015, 5, 2, 2, 31, 22, DateTimeZone.UTC);


CustomEventPayload customEventPayload = CustomEventPayload.newBuilder()
        .setCustomEventBody(customEventBody)
        .setCustomEventUser(customEventUser)
        .setOccurred(occurred)
        .build();

CustomEventRequest customEventRequest = CustomEventRequest.newRequest(customEventPayload);

Response<CustomEventResponse> response = client.execute(customEventRequest);
```


To create a custom event, use the CustomEventRequest.newRequest() method:
