Java-Beispiel: App-Übermittlung mit Spieloptionen und Trailern

Dieser Artikel enthält Java-Codebeispiele, die die Verwendung der Microsoft Store-Übermittlungs-API für diese Aufgaben veranschaulichen:

  • Abrufen eines Azure AD-Zugriffstokens zur Nutzung mit der Microsoft Store-Übermittlungs-API.
  • Erstellen einer App-Übermittlung
  • Konfigurieren von Store-Eintragsdaten für die App-Übermittlung, einschließlich der erweiterten Eintragsoptionen Spiele und Trailer.
  • Hochladen der ZIP-Datei mit den Paketen, Eintragsbildern und Trailerdateien für die App-Übermittlung.
  • Ausführen des Commit für eine App-Übermittlung.

Erstellen einer App-Übermittlung

Die CreateAndSubmitSubmissionExample -Klasse implementiert ein main Programm, das andere Beispielmethoden aufruft, um mithilfe der Microsoft Store-Übermittlungs-API eine App-Übermittlung zu erstellen und zu committen, die Spieloptionen und einen Trailer enthält. So passen Sie den Code für eigene Zwecke an:

  • Weisen Sie die tenantId-Variable zur Mandanten-ID für Ihre App zu und weisen Sie die Variablen clientId und clientSecret zur Client-ID und dem Schlüssel für die App zu. Weitere Informationen finden Sie unter Zuordnen einer Azure AD-Anwendung zu Ihrem Partner Center-Konto.
  • Weisen Sie die applicationId Variable der Store-ID der App zu, für die Sie eine Übermittlung erstellen möchten.
import java.io.IOException;
import java.text.MessageFormat;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import org.apache.http.client.ClientProtocolException;

public class CreateAndSubmitSubmissionExample {

    public static void main(String[] args) {
        
        // Add your tenant ID, client ID, and client secret here.
        String tenantId = "";
        String clientId = "";
        String clientSecret = "";
        DevCenterAccessTokenClient accessTokenClient = new DevCenterAccessTokenClient(tenantId, clientId, clientSecret); 
        String accessToken = accessTokenClient.getAccessToken("https://manage.devcenter.microsoft.com");
        
        // The application ID is taken from your app dashboard page's URI in Dev Center,
        // e.g. https://developer.microsoft.com/en-us/dashboard/apps/{application_id}/
        String applicationId = "";
        DevCenterClient devCenter = new DevCenterClient("https://manage.devcenter.microsoft.com", accessToken);
        
        try {
            // Get the application object, and cancel any in progress submissions.
            JsonObject app = devCenter.getApplicationJsonObject(applicationId);
            JsonObject inProgressSubmission = app.getJsonObject("pendingApplicationSubmission");
            if (inProgressSubmission != null) {
                String inProgressSubmissionId = inProgressSubmission.getString("id");
                devCenter.cancelInProgressSubmission(applicationId, inProgressSubmissionId);
            }
            
            // Create a new submission, based on the last published submission.
            JsonObject submission = devCenter.createSubmission(applicationId);
            String submissionId = submission.getString("id");
            
            // JsonObjects are immutable, so we'll build up our changes to the submission and
            // then merge it with the submission object.
            JsonObjectBuilder submissionChanges = Json.createObjectBuilder();
        
            // The following fields are required:
            submissionChanges.add("applicationCategory", "Games_Fighting");
            submissionChanges.add("listings", getListingsObject());
            submissionChanges.add("pricing", getPricingObject());
            submissionChanges.add("packages", Json.createArrayBuilder().add(getPackageObject()));
            submissionChanges.add("allowTargetFutureDeviceFamilies", getDeviceFamiliesObject());
            
            // Add new Gaming Options to the submission.
            JsonObject gamingOptions = getGamingOptionsJsonObject();
            submissionChanges.add("gamingOptions", Json.createArrayBuilder().add(gamingOptions));
            
            // Add new Trailers to the submission.
            JsonObject trailer = getTrailerObject();
            submissionChanges.add("trailers", Json.createArrayBuilder().add(trailer));
            
            // Continue updating the submission_json object with additional options as needed.
            // After you've finished, call the Update API with the code below to save it:
            JsonObject submissionToUpdate = mergeJsonObjects(submission, submissionChanges.build());
            JsonObject updatedSubmission = devCenter.updateSubmission(applicationId, submissionId, submissionToUpdate);
            
            // All images and packages should be located in a single ZIP file. In the submission JSON, 
            // the file names for all objects requiring them (icons, packages, etc.) must exactly 
            // match the file names from the ZIP file.
            String zipFilePath = "";
            devCenter.uploadZipFileForSubmission(applicationId, submissionId, zipFilePath);
            
            // Committing the submission will start the submission process for it. Once committed,
            // the submission can no longer be changed.
            devCenter.commitSubmission(applicationId, submissionId);
            
            // After committing, you can poll the commit API for the status of the submission's process using
            // the following code.
            boolean waitingForCommitToStart = true;
            while (waitingForCommitToStart) {
                String status = devCenter.getSubmissionStatus(applicationId, submissionId);
                System.out.println(MessageFormat.format("Submission status: {0}", status));
                waitingForCommitToStart = status.equals("CommitStarted");
                if (waitingForCommitToStart) {
                    try {
                        Thread.sleep(60000); // Wait one minute to check Dev Center again.  
                    } catch (InterruptedException iex) {
                        System.out.println("The sleep was interrupted. Checking Dev Center now.");
                    }
                }
            }           
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private static JsonObject mergeJsonObjects(JsonObject baseObject, JsonObject mergeObject) {
        JsonObjectBuilder builder = Json.createObjectBuilder();
        
        for (String baseKey : baseObject.keySet()) {
            if (mergeObject.containsKey(baseKey)) {
                builder.add(baseKey, mergeObject.get(baseKey));
            } else {
                builder.add(baseKey, baseObject.get(baseKey));
            }
        }
        
        for (String mergeKey : mergeObject.keySet()) {
            if (!baseObject.containsKey(mergeKey)) {
                builder.add(mergeKey, mergeObject.get(mergeKey));
            }
        }
        
        return builder.build();
    }
    
    private static JsonObject getListingsObject() {
        // This structure holds basic information to display in the store.
        JsonObjectBuilder baseListing = Json.createObjectBuilder();
        baseListing.add("copyrightAndTrademarkInfo", "(C) 2017 Microsoft");
        baseListing.add("licenseTerms", "http://example.com/licenseTerms.aspx");
        baseListing.add("privacyPolicy", "http://example.com/privacyPolicy.aspx");
        baseListing.add("supportContact", "support@example.com");
        baseListing.add("websiteUrl", "http://example.com");
        baseListing.add("description", "A sample game showing off gameplay options code.");
        baseListing.add("releaseNotes", "Initial release");
                
        // The title of the app must match a reserved name for the app in Dev Center.
        // If it doesn't, attempting to update the submission will fail.
        baseListing.add("title", "Super Dev Center API Simulator 2017");
        
        // Up to 7 keywords may be provided in a listing.
        JsonArrayBuilder keywords = Json.createArrayBuilder();
        keywords.add("SampleApp").add("SampleFightingGame").add("GameOptions");
        baseListing.add("keywords", keywords);
        
        JsonArrayBuilder features = Json.createArrayBuilder();
        features.add("Doesn't crash");
        features.add("Likes to eat chips");
        baseListing.add("features", features);
        
        // If your app works better with specific hardware (or needs it), you can
        // add or update values here.
        JsonArrayBuilder hardwarePreferences = Json.createArrayBuilder();
        hardwarePreferences.add("Keyboard");
        hardwarePreferences.add("Mouse");
        baseListing.add("hardwarePreferences", hardwarePreferences);
        
        JsonArrayBuilder images = Json.createArrayBuilder();
        
        // There are several types of images available; at least one screenshot
        // is required.
        JsonObjectBuilder image = Json.createObjectBuilder();
        image.add("fileName", "tile.png");
        image.add("description", "The tile as it appears in the store.");
        image.add("imageType", "Icon");
        images.add(image);
        baseListing.add("images", images);
        
        JsonObjectBuilder listing = Json.createObjectBuilder();
        listing.add("baseListing", baseListing);
        
        // If there are any specific overrides to above information for Windows 8,
        // Windows 8.1, Windows Phone 7.1, 8.0, or 8.1, you can add information here.
        listing.add("platformOverrides", Json.createObjectBuilder());
        
        JsonObjectBuilder listings = Json.createObjectBuilder();
        // Each listing is targeted at a specific language-locale code, e.g. EN-US.
        listings.add("en-us", listing);
        return listings.build();
    }
    
    private static JsonObject getPackageObject() {
        JsonObjectBuilder pkg = Json.createObjectBuilder();
        // The file name is relative to the root of the uploaded ZIP file.
        pkg.add("fileName", "bin/super_dev_ctr_api_sim.appxupload");
        
        // If you haven't begun to upload the file yet, set this value to "PendingUpload".
        pkg.add("fileStatus", "PendingUpload");
        return pkg.build();
    }
    
    private static JsonObject getPricingObject() {
        JsonObjectBuilder pricing = Json.createObjectBuilder();
        
        // How long the trial period is, if one is allowed. Valid values are NoFreeTrial,
        // OneDay, SevenDays, FifteenDays, ThirtyDays, or TrialNeverExpires.
        pricing.add("trialPeriod", "NoFreeTrial");
        
        // Maps to the default price for the app.
        pricing.add("priceId", "Free");
        
        // If you'd like to offer your app in different markets at different prices, you
        // can provide priceId values per language/locale code.
        pricing.add("marketSpecificPricing", Json.createObjectBuilder().build());
        return pricing.build();
    }
    
    private static JsonObject getDeviceFamiliesObject() {
        JsonObjectBuilder futureDeviceFamilies = Json.createObjectBuilder();
        
        // Supported values are Desktop, Mobile, Xbox, and Holographic. To make
        // the app available on that specific platform, set the value to True.
        futureDeviceFamilies.add("Desktop", true);
        futureDeviceFamilies.add("Mobile", false);
        futureDeviceFamilies.add("Xbox", true);
        futureDeviceFamilies.add("Holographic", false);
        return futureDeviceFamilies.build();
    }


    private static JsonObject getGamingOptionsJsonObject() {
        JsonObjectBuilder gamingOptions = Json.createObjectBuilder();
        
        // The genres of your game.
        JsonArrayBuilder genres = Json.createArrayBuilder();
        genres.add("Games_Fighting");
        gamingOptions.add("genres", genres);
        
        // Set this to true if your game supports local multiplayer. This field
        // is required.
        gamingOptions.add("isLocalMultiplayer", true);
        
        // If local multiplayer is supported, you must provide the minimum and
        // maximum players supported. Valid values are between 2 and 1000 inclusive.
        gamingOptions.add("localMultiplayerMinPlayers", 2);
        gamingOptions.add("localMultiplayerMaxPlayers", 4);
        
        // Set this to True if your game supports local co-op play. This field is required.
        gamingOptions.add("isLocalCooperative", true);

        // If local co-op is supported, you must provide the minimum and maximum players
        // supported. Valid values are between 2 and 1000 inclusive.            
        gamingOptions.add("localCooperativeMinPlayers", 2);
        gamingOptions.add("localCooperativeMaxPlayers", 4);
        
        // Set this to True if your game supports online multiplayer. This field is required.
        gamingOptions.add("isOnlineMultiplayer", true);
        
        // If online multiplayer is supported, you must provide the minimum and maximum players
        // supported. Valid values are between 2 and 1000 inclusive.
        gamingOptions.add("onlineMultiplayerMinPlayers", 2);
        gamingOptions.add("onlineMultiplayerMaxPlayers", 4);
        
        // Set this to true if your game supports online co-op play. This field is required. 
        gamingOptions.add("isOnlineCooperative", true);
        
        // If online co-op is supported, you must provide the minimum and maximum players
        // supported. Valid values are between 2 and 1000 inclusive.
        gamingOptions.add("onlineCooperativeMinPlayers", 2);
        gamingOptions.add("onlineCooperativeMaxPlayers", 4);
        
        // If your game supports broadcasting a stream to other players, set this field to True.
        // This field is required.
        gamingOptions.add("isBroadcastingPrivilegeGranted", true);
        
        // If your game supports cross-device play (e.g. a player can play on an Xbox One with
        // their friend who's playing on a PC), set this field to True. This field is required.
        gamingOptions.add("isCrossPlayEnabled", true);
        
        // If your game supports Kinect usage, set this field to "Enabled", otherwise, set it to
        // "Disabled". This field is required.
        gamingOptions.add("kinectDataForExternal", "Disabled");
        
        // Free text about any other peripherals that your game supports. This field is optional.
        gamingOptions.add("otherPeripherals", "Supports the usage of all fighting joysticks.");
        
        return gamingOptions.build();
    }
    
    private static JsonObject getTrailerObject() {
        JsonObjectBuilder trailer = Json.createObjectBuilder();

        // This is the filename of the trailer. The file name is a relative path to the
        // root of the ZIP file to be uploaded to the API.
        trailer.add("VideoFileName", "trailers/main/my_awesome_trailer.mpeg");
        
        // Aside from the video itself, a trailer can have metadata assets including a title and images 
        // such as screenshots or alternate images. These are keyed by market code (see the end of this
        // method for an example.
        JsonObjectBuilder trailerAssetsByCountry = Json.createObjectBuilder();
                
        JsonObjectBuilder trailerAssetSet = Json.createObjectBuilder();
                
        // The title of the trailer to display in the store.
        trailerAssetSet.add("Title", "Main Trailer");
        
        // The list of images provided with the trailer that are shown when the trailer isn't playing.
        JsonArrayBuilder trailerImageAssets = Json.createArrayBuilder();
        
        JsonObjectBuilder mainTrailerImage = Json.createObjectBuilder();
        
        // The file name of the image. The file name is a relative path to the root of the ZIP
        // file to be uploaded to the API.
        mainTrailerImage.add("FileName", "trailers/main/thumbnail.png");
        
        // A plaintext description of what the image represents.
        mainTrailerImage.add("Description", "The thumbnail for the trailer shown before the user clicks play");
        trailerImageAssets.add(mainTrailerImage);
        
        // Add a second image.
        JsonObjectBuilder altImage = Json.createObjectBuilder();
        altImage.add("FileName", "trailers/main/alt-img.png");
        altImage.add("Description", "The image to show after the trailer plays");
        trailerImageAssets.add(altImage);
        
        trailerAssetSet.add("ImageList", trailerImageAssets);
        
        // This line creates the trailer asset for en-us.
        trailerAssetsByCountry.add("en-us", trailerAssetSet);
        
        trailer.add("TrailerAssets", trailerAssetsByCountry);
        
        return trailer.build();
    }
}

Abrufen eines Azure AD-Zugriffstokens

Die DevCenterAccessTokenClient-Klasse definiert eine Hilfsmethode, die Ihre tenantId, clientId und clientSecret Werte zum Erstellen eines Azure AD-Zugriffstokens zur Verwendung mit Microsoft Store-Übermittlungs-API verwendet.

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.MessageFormat;
import javax.json.Json;
import javax.json.JsonReader;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

/**
 * A client for getting access tokens to the Dev Center API.
 * @author Microsoft
 */
public final class DevCenterAccessTokenClient {
    private String tenantId;
    private String clientId;
    private String clientSecret;
    
    /**
     * Creates a new access token client for Dev Center.
     * @param tenantId Your tenant ID for the app.
     * @param clientId Your client ID for the app.
     * @param clientSecret Your client secret string for the app.
     */
    public DevCenterAccessTokenClient(String tenantId, String clientId, String clientSecret) {
        this.tenantId = tenantId;
        this.clientId = clientId;
        this.clientSecret = clientSecret;
    }
    
    /**
     * Gets an access token for the specific resource.
     * @param resource The full URI to the resource to be accessed.
     * @return An access token for that resource, good for one hour.
     */
    public String getAccessToken(String resource) {        
        // Generate access token. Access token is valid for 1 hour.
        String tokenEndpoint = "https://login.microsoftonline.com/{0}/oauth2/token";       

        HttpPost tokenRequest = new HttpPost(MessageFormat.format(tokenEndpoint, this.tenantId));
        String tokenRequestBody = MessageFormat.format("grant_type=client_credentials&client_id={0}&client_secret={1}&resource={2}", this.clientId, this.clientSecret, resource);
        tokenRequest.setEntity(new StringEntity(tokenRequestBody, "utf-8"));
        tokenRequest.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
        
        CloseableHttpClient httpclient = HttpClients.createDefault();
        ResponseHandler<String> responseHandler = new BasicResponseHandler();
        
        try {
            String tokenResponse = httpclient.execute(tokenRequest, responseHandler);
            JsonReader reader = Json.createReader(new ByteArrayInputStream(tokenResponse.getBytes("UTF-8")));
            String accessToken = reader.readObject().getString("access_token");
            
            return accessToken;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        return null;
    }
}

Hilfsmethoden zum Aufrufen der Übermittlungs-API und Hochladen von Übermittlungsdateien

Die DevCenterClient Klasse definiert Hilfsmethoden, die eine Vielzahl von Methoden in der Microsoft Store-Übermittlungs-API aufrufen und die ZIP-Datei mit den Paketen, Bildern und Trailerdateien für die App-Übermittlung hochladen.

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.text.MessageFormat;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonReader;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.FileEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

/**
 * A client for accessing commands in the Dev Center API.
 * @author Microsoft
 */
public final class DevCenterClient implements ResponseHandler<JsonObject> {
    private String baseUri;
    private String accessToken;
    
    /**
     * Creates a new dev center client instance.
     * @param baseUri The base URI for the Dev Center API.
     * @param accessToken The access token for web call authentication.
     */
    public DevCenterClient(String baseUri, String accessToken) {
        this.baseUri = baseUri;
        this.accessToken = accessToken;
    }
    
    /**
     * Returns the application JSON object from the Dev Center API.
     * @param applicationId The application ID. 
     * @return A JSON object from Dev Center.
     * @throws IOException Thrown when a serialization exception occurs on the read.
     * @throws ClientProtocolException Thrown when an HTTP communication error occurs.
     */
    public JsonObject getApplicationJsonObject(String applicationId) throws ClientProtocolException, IOException {
        String path = MessageFormat.format("/v1.0/my/applications/{0}", applicationId);
        return get(path);
    }
    
    /**
     * Cancels and deletes the in-progress submission from the application.
     * @param applicationId The application ID.
     * @param submissionId The submission ID.
     * @return A JSON object from Dev Center.
     * @throws IOException Thrown when a serialization exception occurs on the read.
     * @throws ClientProtocolException Thrown when an HTTP communication error occurs.
     */
    public JsonObject cancelInProgressSubmission(String applicationId, String submissionId) throws ClientProtocolException, IOException {
        String path = MessageFormat.format("/v1.0/my/applications/{0}/submissions/{1}", applicationId, submissionId);
        return delete(path);
    }
    
    /**
     * Creates a new submission in Dev Center.
     * @param applicationId The application ID.
     * @return The submission JSON object from Dev Center.
     * @throws IOException Thrown when a serialization exception occurs on the read.
     * @throws ClientProtocolException Thrown when an HTTP communication error occurs.
     */
    public JsonObject createSubmission(String applicationId) throws ClientProtocolException, IOException {
        String path = MessageFormat.format("/v1.0/my/applications/{0}/submissions", applicationId);
        return post(path);
    }
    
    /**
     * Updates the submission in Dev Center.
     * @param applicationId The application ID.
     * @param submissionId The submission ID.
     * @param submission The submission JSON object.
     * @return The updated submission JSON object from Dev Center. 
     * @throws IOException Thrown when a serialization exception occurs on the read.
     * @throws ClientProtocolException Thrown when an HTTP communication error occurs.
     */
    public JsonObject updateSubmission(String applicationId, String submissionId, JsonObject submission) throws ClientProtocolException, IOException {
        String path = MessageFormat.format("/v1.0/my/applications/{0}/submissions/{1}", applicationId, submissionId);
        return put(path, submission);
    }
    
    /**
     * Gets the submission in Dev Center.
     * @param applicationId The application ID.
     * @param submissionId The submission ID.
     * @return The submission JSON object from Dev Center. 
     * @throws IOException Thrown when a serialization exception occurs on the read.
     * @throws ClientProtocolException Thrown when an HTTP communication error occurs.
     */
    public JsonObject getSubmission(String applicationId, String submissionId) throws ClientProtocolException, IOException {
        String path = MessageFormat.format("/v1.0/my/applications/{0}/submissions/{1}", applicationId, submissionId);
        return get(path);
    }
    
    /**
     * Commits the submission to Dev Center.
     * @param applicationId The application ID.
     * @param submissionId The submission ID.
     * @throws IOException Thrown when a serialization exception occurs on the read.
     * @throws ClientProtocolException Thrown when an HTTP communication error occurs.
     */
    public void commitSubmission(String applicationId, String submissionId) throws ClientProtocolException, IOException {
        String path = MessageFormat.format("/v1.0/my/applications/{0}/submissions/{1}/commit", applicationId, submissionId);
        post(path);
    }
    
    /**
     * Returns the submission status of this submission.
     * @param applicationId The application ID.
     * @param submissionId The submission ID.
     * @return The status of the submission in Dev Center.
     * @throws IOException Thrown when a serialization exception occurs on the read.
     * @throws ClientProtocolException Thrown when an HTTP communication error occurs.
     */
    public String getSubmissionStatus(String applicationId, String submissionId) throws ClientProtocolException, IOException {
        JsonObject response = getSubmission(applicationId, submissionId);
        String status = response.getString("status");
        if (status == null || status.isEmpty())
        {
            return "Unknown";
        }
        
        return status;
    }
    
    /**
     * Uploads a ZIP archive containing the binaries, image assets, trailers, and other components to the submission.
     * @param applicationId The application ID.
     * @param submissionId The submission ID.
     * @param zipFilePath The file path to the ZIP file.
     * @throws IOException Thrown when a serialization exception occurs on the read.
     * @throws ClientProtocolException Thrown when an HTTP communication error occurs.
     */
    public void uploadZipFileForSubmission(String applicationId, String submissionId, String zipFilePath) throws ClientProtocolException, IOException {
        JsonObject submission = getSubmission(applicationId, submissionId); 
        String fileUploadUrl = submission.getString("fileUploadUri");
        
        CloseableHttpClient httpclient = HttpClients.createDefault();
        File uploadFile = new File(zipFilePath);
        HttpPut uploadFileRequest = new HttpPut(fileUploadUrl.replace("+", "%2B")); // Encode '+', otherwise it will be decoded as ' ' 
        uploadFileRequest.addHeader("x-ms-blob-type", "BlockBlob");
        uploadFileRequest.setEntity(new FileEntity(uploadFile));
        httpclient.execute(uploadFileRequest);
    }
    
    private JsonObject get(String path) throws ClientProtocolException, IOException {
        HttpGet request = new HttpGet(this.baseUri + path);
        return invoke(request);
    }
    
    private JsonObject put(String path, JsonObject obj) throws ClientProtocolException, IOException {
        HttpPut request = new HttpPut(this.baseUri + path);
        request.addHeader("Content-Type", "application/json; charset=utf-8");
        request.setEntity(new StringEntity(SerializeJsonObject(obj)));
        return invoke(request);
    }
    
    private JsonObject post(String path) throws ClientProtocolException, IOException {
        return post(path, null);
    }
    
    private JsonObject post(String path, JsonObject obj) throws ClientProtocolException, IOException {
        HttpPost request = new HttpPost(this.baseUri + path);
        request.addHeader("Content-Type", "application/json; charset=utf-8");
        if (obj != null) 
        {
            request.setEntity(new StringEntity(SerializeJsonObject(obj)));        
        }
        return invoke(request);
    }
    
    private JsonObject delete(String path) throws ClientProtocolException, IOException {
        HttpDelete request = new HttpDelete(this.baseUri + path);
        return invoke(request);
    }
    
    private JsonObject invoke(HttpUriRequest request) throws ClientProtocolException, IOException {
        CloseableHttpClient client = HttpClients.createDefault();    
        request.addHeader("Authorization", "Bearer " + accessToken);
        request.addHeader("User-Agent", "Java");
        JsonObject response = client.execute(request, this);
        return response;
    }   

    public JsonObject handleResponse(HttpResponse response) throws ClientProtocolException, IOException  {
        StatusLine status = response.getStatusLine();
        int statusCode = status.getStatusCode();
        String reasonPhrase = status.getReasonPhrase();
        HttpEntity entity = response.getEntity();
        JsonObject returnValue = null;
        if(entity != null && entity.getContentLength() != 0) {
            JsonReader reader = Json.createReader(entity.getContent());
            returnValue = reader.readObject();
        }
        
        if (statusCode < 200 || statusCode > 299)  {
            if (returnValue != null) {
                throw new HttpResponseException(statusCode, returnValue.toString());
            }
            
            throw new HttpResponseException(statusCode, reasonPhrase);
        }
        
        return returnValue;
    }
    
    private static String SerializeJsonObject(JsonObject obj) {
        StringWriter writer = new StringWriter();
        Json.createWriter(writer).writeObject(obj);
        String body = writer.toString();
        return body;
    }
}