/*
*
* * Copyright 2013 Jive Software
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package com.jivesoftware.sdk.client;
import com.jivesoftware.sdk.api.entity.TileInstance;
import com.jivesoftware.sdk.api.entity.TileInstanceProvider;
import com.jivesoftware.sdk.api.tile.data.ActivityEntry;
import com.jivesoftware.sdk.api.tile.data.ActivityPushTile;
import com.jivesoftware.sdk.client.oauth.JiveOAuthClient;
import com.jivesoftware.sdk.utils.JiveSDKUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import javax.inject.Singleton;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.client.AsyncInvoker;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
*
* NOTE: HAD TO MOVE AWAY FROM GENERICS SO I CAN DO INJECTIONS
* Created by rrutan on 2/3/14.
*/
@Component
@Singleton
public class JiveTileClient extends BaseJiveClient {
private static final Logger log = LoggerFactory.getLogger(JiveTileClient.class);
@Autowired @Qualifier("tileInstanceProvider")
private TileInstanceProvider tileInstanceProvider;
@Autowired
private JiveOAuthClient jiveOAuthClient;
public JiveTileClient() {
if (log.isTraceEnabled()) { log.trace("constructor called..."); }
} // end constructor
/**
* Update data in tile instance.
*/
public void pushData(TileInstance tileInstance, Object data) throws JiveClientException {
if (log.isTraceEnabled()) { log.trace("pushData called..."); }
if (log.isDebugEnabled()) { log.debug("pushData => ["+tileInstance.getJivePushUrl()+"]"); }
try {
DataBlock dataBlock = new DataBlock(data);
makeRequest(tileInstance, tileInstance.getJivePushUrl(), HttpMethod.PUT, dataBlock, String.class);
}
catch (TargetGoneException e) {
log.info("Received 410 from data push request, assuming tile deleted in Jive and removing it, instance url: " + tileInstance.getJivePushUrl());
try {
tileInstanceProvider.remove(tileInstance);
}
catch (TileInstanceProvider.TileInstanceProviderException pe) {
log.error("Failed to remove tile instance", pe);
}
}
}
public Object fetchData(TileInstance tileInstance, Class clazz) throws JiveClientException {
initAccessTokens(tileInstance);
Client client = buildClient();
//TODO: CONFIRM THE URL NEEDED TO FETCH DATA, PRETTY SURE THIS ISNT jivePushURL
WebTarget target = client.target(tileInstance.getJivePushUrl());
AsyncInvoker asyncInvoker = target.request(MediaType.APPLICATION_JSON_TYPE)
.header(HttpHeaders.AUTHORIZATION,tileInstance.getCredentials().getAuthorizationHeader())
.async();
Future<Response> responseFuture = asyncInvoker.get();
Response response = null;
try {
response = responseFuture.get();
//TODO: CHECK THAT THIS IS THE CORRECT RESPONSE CODE
if (response.getStatus() == 204) {
//TODO: CONFIRM THE URL NEEDED TO FETCH DATA, PRETTY SURE THIS ISNT jivePushURL
if (log.isInfoEnabled()) { log.info("Successful Fetch ["+tileInstance.getJivePushUrl()+"]"); }
//TODO: EXPORT TO UTILITY CLASS
//TODO: NEED TO USE JACKSON AND CONVERT ENTITY TO ITS ORIGINAL OBJECT TYPE?
return response.getEntity();
} // end if
} catch (BadRequestException bre) {
log.error("Error Fetching Data From Tile [" + tileInstance.getJivePushUrl() + "]", bre);
throw JiveClientException.buildException("Error Fetching Data to Tile [" + tileInstance.getJivePushUrl() + "]",bre,tileInstance,null,clazz);
} catch (InterruptedException ie) {
log.error("Error Fetching Data From Tile [" + tileInstance.getJivePushUrl() + "]", ie);
throw JiveClientException.buildException("Error Fetching Data to Tile [" + tileInstance.getJivePushUrl() + "]",ie,tileInstance,null,clazz);
} catch (ExecutionException ee) {
log.error("Error Fetching Data From Tile [" + tileInstance.getJivePushUrl() + "]", ee);
throw JiveClientException.buildException("Error Fetching Data to Tile [" + tileInstance.getJivePushUrl() + "]",ee,tileInstance,null,clazz);
} // end try/catch
asyncInvoker = null;
target = null;
client.close();
client = null;
return null;
} // end fetchData
/**
* Post new external activity object into activity tile stream. Returns the created ActivityEntry.
*
* @param tileInstance Instance of activity tile stream
* @param data External activity object content
*/
public ActivityEntry pushActivity(TileInstance tileInstance, ActivityPushTile data) throws JiveClientException {
ActivityEntry created = null;
try {
created = makeRequest(tileInstance, tileInstance.getJivePushUrl(), HttpMethod.POST, data, ActivityEntry.class);
}
catch (TargetGoneException e) {
log.info("Received 410 from activity push request, assuming tile deleted in Jive and removing it, instance url: " + tileInstance.getJivePushUrl());
try {
tileInstanceProvider.remove(tileInstance);
}
catch (TileInstanceProvider.TileInstanceProviderException pe) {
log.error("Failed to remove tile instance", pe);
}
}
return created;
}
/**
* Update existing external activity object in an activity tile stream. Returns the updated ActivityEntry.
*
* @param tileInstance Instance of activity tile stream
* @param externalObjectUrl API url for the external object
* @param data External activity object content
*
* @throws TargetGoneException Thrown when receiving a 410 response. This means that external activity object has been deleted in Jive.
*/
public ActivityEntry updateActivity(TileInstance tileInstance, String externalObjectUrl, ActivityPushTile data) throws JiveClientException {
return makeRequest(tileInstance, externalObjectUrl, HttpMethod.PUT, data, ActivityEntry.class);
}
/**
* Make http request to url, with given method and content. Tile instance is used to insert
* auth headers into the request.
*
* @param tileInstance Tile instance where data is pushed, or the parent activity stream tile, when updating external activity object
* @param url Full url to request target
* @param methodName String name of http method
* @param content Request content, will be sent as JSON
* @param returnType Class type of response entity. Use String.class if you dont care about the response
* @throws TargetGoneException Thrown when jive responds with 410, meaning the object identified by url has been removed
*/
public <T> T makeRequest(TileInstance tileInstance, String url, String methodName, Object content, Class<T> returnType) throws TargetGoneException {
initAccessTokens(tileInstance);
Client client = buildClient();
WebTarget target = client.target(tileInstance.getJivePushUrl());
AsyncInvoker asyncInvoker = target.request(MediaType.APPLICATION_JSON_TYPE)
.header(HttpHeaders.AUTHORIZATION, tileInstance.getCredentials().getAuthorizationHeader())
.async();
Entity contentEntity = Entity.entity(content, MediaType.APPLICATION_JSON_TYPE);
Future<Response> responseFuture = asyncInvoker.method(methodName, contentEntity);
Response response = null;
try {
response = responseFuture.get();
if (response.getStatus() == Response.Status.GONE.getStatusCode()) {
// target (tile instance, external stream object, etc..) has been removed from Jive
// throw a checked exception so that caller can react accordingly
throw new TargetGoneException("", tileInstance);
}
return response.readEntity(returnType);
}
catch (ForbiddenException fe) {
log.debug("403 response from a " + methodName + " to " + url);
refreshAccessTokens(tileInstance);
return makeRequest(tileInstance, url, methodName, content, returnType); // recursive call to retry
}
catch (InterruptedException e) {
// todo useless to catch these... refactor somehow
log.error("Error when making a " + methodName + " to " + url, e);
throw new RuntimeException("Failed to make a request to jive");
}
catch (ExecutionException e) {
log.error("Error when making a " + methodName + " to " + url, e);
throw new RuntimeException("Failed to make a request to jive");
}
finally {
if (response != null ) {
response.close();
}
client.close();
}
}
private void refreshAccessTokens(TileInstance tileInstance) {
tileInstance.setCredentials(jiveOAuthClient.refreshTileInstanceAccessToken(tileInstance));
try {
tileInstanceProvider.update(tileInstance);
if (log.isDebugEnabled()) { log.debug("Successfully Updated Tile Instance!"); }
} catch (TileInstanceProvider.TileInstanceProviderException tipe) {
log.error("Unable to save credential information",tipe);
}
}
private void initAccessTokens(TileInstance tileInstance) {
if (log.isTraceEnabled()) { log.trace("Initializing OAuth Access Tokens"); }
if (!JiveSDKUtils.isAllExist(tileInstance.getCredentials().getRefreshToken())) {
if (log.isDebugEnabled()) { log.debug("Refresh OAuth Token Missing ..."); }
tileInstance.setCredentials(jiveOAuthClient.getTileInstanceAccessToken(tileInstance));
//TODO: SHOULD I SAVE EXPIRES TIME / CALCULATE AND REFRESH PROACTIVELY?
try {
tileInstanceProvider.update(tileInstance);
if (log.isDebugEnabled()) { log.debug("Successfully Updated Tile Instance!"); }
} catch (TileInstanceProvider.TileInstanceProviderException tipe) {
log.error("Unable to save credential information",tipe);
} // end try/catch
} // end if
} // end initAccessTokens
} // end JiveTileClient