/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.bsc.confluence.rest;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.MalformedURLException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonReader;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.bsc.confluence.ConfluenceService;
import org.bsc.confluence.ExportFormat;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Func1;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import org.bsc.confluence.rest.model.Page;
import javax.json.JsonObjectBuilder;
import static java.lang.String.format;
import org.bsc.ssl.SSLCertificateInfo;
import rx.functions.Action1;
/**
* @see https://docs.atlassian.com/confluence/REST/latest/
*
* @author bosrrentino
*/
public class RESTConfluenceServiceImpl implements ConfluenceService {
final Credentials credentials;
final OkHttpClient.Builder client = new OkHttpClient.Builder();
final java.net.URL endpoint ;
public RESTConfluenceServiceImpl( String url , Credentials credentials, SSLCertificateInfo sslInfo) {
if( credentials==null ) {
throw new IllegalArgumentException("credentials argument is null!");
}
if( url==null ) {
throw new IllegalArgumentException("url argument is null!");
}
if( sslInfo==null ) {
throw new IllegalArgumentException("sslInfo argument is null!");
}
try {
this.endpoint = new java.net.URL(url);
} catch (MalformedURLException ex) {
throw new IllegalArgumentException("url argument is not valid!", ex);
}
this.credentials = credentials;
client.connectTimeout(10, TimeUnit.SECONDS);
client.writeTimeout(10, TimeUnit.SECONDS);
client.readTimeout(30, TimeUnit.SECONDS);
if( !sslInfo.isIgnore() && "https".equals(this.endpoint.getProtocol()) ) {
client.hostnameVerifier(sslInfo.getHostnameVerifier())
.sslSocketFactory(sslInfo.getSSLSocketFactory(), sslInfo.getTrustManager())
;
}
}
private HttpUrl.Builder urlBuilder() {
int port = endpoint.getPort();
port = (port > -1 ) ? port : endpoint.getDefaultPort();
final String path = ConfluenceService.Protocol.XMLRPC.removeFrom(endpoint.getPath());
return new HttpUrl.Builder()
.scheme(endpoint.getProtocol())
.host(endpoint.getHost())
.port(port)
.addPathSegments(path)
.addPathSegments(ConfluenceService.Protocol.REST.path())
;
}
private Observable<Response> rxfindPagesResponse( final String spaceKey, final String title ) {
final String credential =
okhttp3.Credentials.basic(credentials.username, credentials.password);
final HttpUrl url = urlBuilder()
.addPathSegment("content")
.addQueryParameter("spaceKey", spaceKey)
.addQueryParameter("title", title)
.addQueryParameter("expand", "space,version,container")
.build();
final Request req = new Request.Builder()
.header("Authorization", credential)
.url( url )
.get()
.build();
return rx.Observable.create( new Observable.OnSubscribe<Response>() {
@Override
public void call(Subscriber<? super Response> t) {
try {
final Response res = client.build().newCall(req).execute();
t.onNext(res);
t.onCompleted();
} catch (IOException ex) {
t.onError(ex);
}
}
});
}
public Observable<JsonArray> rxfindPagesArray( final String spaceKey, final String title ) {
return rxfindPagesResponse(spaceKey, title)
.flatMap( new Func1<Response, Observable<? extends JsonArray>>() {
@Override
public Observable<? extends JsonArray> call(Response res) {
final ResponseBody body = res.body();
try (Reader r = body.charStream()) {
final JsonReader rdr = Json.createReader(r);
final JsonObject root = rdr.readObject();
// {"statusCode":404,"data":{"authorized":false,"valid":true,"errors":[],"successful":false,"notSuccessful":true},"message":"No space with key : TEST"}
final JsonArray results = root.getJsonArray("results");
//System.out.println( root.toString() );
if( results == null ) {
return Observable.empty();
}
return Observable.just(results);
} catch (IOException ex) {
return Observable.error(ex);
}
}
});
}
public Observable<JsonObject> rxfindPage( final String spaceKey, final String title ) {
return rxfindPagesArray(spaceKey, title).flatMap(new Func1<JsonArray, Observable<? extends JsonObject>>() {
@Override
public Observable<? extends JsonObject> call(JsonArray t) {
if( t.size() == 1 ) {
return Observable.just( t.getJsonObject(0) );
}
if( t.size() == 0 ) {
return Observable.empty();
}
return Observable.error( new Exception( format("results contains more than one element [%d]", t.size()) ));
}
});
}
public JsonObjectBuilder jsonForCreatingPage( final String spaceKey, final String title ) {
return Json.createObjectBuilder()
.add("type","page")
.add("title",title)
.add("space",Json.createObjectBuilder().add("key", spaceKey))
;
}
public JsonObjectBuilder jsonForCreatingPage( final String spaceKey, final int parentPageId, final String title ) {
return jsonForCreatingPage( spaceKey, title )
.add("ancestors", Json.createArrayBuilder()
.add(Json.createObjectBuilder().add("id", parentPageId )))
;
}
public JsonObjectBuilder jsonAddBody( JsonObjectBuilder builder, Storage storage ) {
return builder
.add("body", Json.createObjectBuilder()
.add("storage", Json.createObjectBuilder()
.add("representation",storage.rapresentation.toString())
.add("value",storage.value)))
;
}
public Observable<JsonObject> rxCreatePage( final JsonObject inputData ) {
final String credential =
okhttp3.Credentials.basic(credentials.username, credentials.password);
final MediaType storageFormat = MediaType.parse("application/json");
final RequestBody inputBody = RequestBody.create(storageFormat,
inputData.toString());
final Request req = new Request.Builder()
.header("Authorization", credential)
.url( urlBuilder().addPathSegment("content") .build() )
.post(inputBody)
.build();
return rx.Observable.create( new Observable.OnSubscribe<JsonObject>() {
@Override
public void call(Subscriber<? super JsonObject> t) {
try {
final Response res = client.build().newCall(req).execute();
if( !res.isSuccessful() ) {
t.onError( new Exception( format("error creating page\n%s", res.toString()) ));
return;
}
final ResponseBody body = res.body();
try( Reader r = body.charStream()) {
final JsonReader rdr = Json.createReader(r);
final JsonObject root = rdr.readObject();
t.onNext(root);
t.onCompleted();
}
} catch (IOException ex) {
t.onError(ex);
}
}
});
}
public Observable<JsonObject> rxUpdatePage( final String pageId, final JsonObject inputData ) {
final String credential =
okhttp3.Credentials.basic(credentials.username, credentials.password);
final MediaType storageFormat = MediaType.parse("application/json");
final RequestBody inputBody = RequestBody.create(storageFormat,
inputData.toString());
final Request req = new Request.Builder()
.header("Authorization", credential)
.url( urlBuilder()
.addPathSegment("content")
.addPathSegment(pageId)
.build() )
.put(inputBody)
.build();
return rx.Observable.create( new Observable.OnSubscribe<JsonObject>() {
@Override
public void call(Subscriber<? super JsonObject> t) {
try {
final Response res = client.build().newCall(req).execute();
if( !res.isSuccessful() ) {
t.onError( new Exception( format("error updating page\n%s", res.toString()) ));
return;
}
final ResponseBody body = res.body();
try( Reader r = body.charStream()) {
final JsonReader rdr = Json.createReader(r);
final JsonObject root = rdr.readObject();
t.onNext(root);
t.onCompleted();
}
} catch (IOException ex) {
t.onError(ex);
}
}
});
}
@Override
public Credentials getCredentials() {
return credentials;
}
@Override
public Model.PageSummary findPageByTitle(String parentPageId, String title) throws Exception {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public boolean removePage(Model.Page parentPage, String title) throws Exception {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public void removePage(String pageId) throws Exception {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public Model.Page getOrCreatePage(final String spaceKey, final String parentPageTitle, final String title) throws Exception {
final Observable error = Observable.error(new Exception(format("parentPage [%s] doesn't exist!",parentPageTitle)));
final JsonObject page =
(JsonObject) rxfindPage(spaceKey, parentPageTitle)
.switchIfEmpty( error )
.flatMap(new Func1<JsonObject, Observable<JsonObject>>() {
@Override
public Observable<JsonObject> call(JsonObject parent) {
final String id = parent.getString("id");
final JsonObjectBuilder input = jsonForCreatingPage(spaceKey, Integer.valueOf(id), title);
return rxfindPage(spaceKey,title)
.switchIfEmpty( rxCreatePage( input.build() ));
}
})
.toBlocking()
.first()
;
return new Page( page );
}
@Override
public Model.Page getOrCreatePage(Model.Page parentPage, String title) throws Exception {
final String spaceKey = parentPage.getSpace();
final String id = parentPage.getId();
final JsonObjectBuilder input = jsonForCreatingPage(spaceKey, Integer.valueOf(id), title);
final JsonObject result = rxfindPage(spaceKey,title)
.switchIfEmpty( rxCreatePage( input.build() ))
.toBlocking().first();
return new Page(result);
}
@Override
public Model.Page getPage(String pageId) throws Exception {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public Model.Page getPage(String spaceKey, String pageTitle) throws Exception {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public boolean addLabelByName(String label, long id) throws Exception {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public Model.Page storePage(Model.Page page, Storage content ) throws Exception {
int previousVersion = page.getVersion();
final JsonObject input = Json.createObjectBuilder()
.add("version", Json.createObjectBuilder().add("number", ++previousVersion))
.add("id",page.getId())
.add("type","page")
.add("title",page.getTitle())
.add("space",Json.createObjectBuilder().add("key", page.getSpace()))
.add("body", Json.createObjectBuilder()
.add("storage", Json.createObjectBuilder()
.add("representation",content.rapresentation.toString())
.add("value",content.value)))
.build()
;
final JsonObject result = rxUpdatePage(page.getId(),input)
.toBlocking()
.first();
return new Page(result);
}
@Override
public Model.Page storePage(Model.Page page) throws Exception {
return page;
}
@Override
public List<Model.PageSummary> getDescendents(String pageId) throws Exception {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public void exportPage(String url, String spaceKey, String pageTitle, ExportFormat exfmt, File outputFile) throws Exception {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public void call(Action1<ConfluenceService> task) throws Exception {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public Model.Attachment createAttachment() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public Model.Attachment getAttachment(String pageId, String name, String version) throws Exception {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public Model.Attachment addAttchment(Model.Page page, Model.Attachment attachment, InputStream source) throws Exception {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}