package nsf.playground.beans;
import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
import lotus.domino.Database;
import lotus.domino.Document;
import lotus.domino.NotesException;
import lotus.domino.ViewEntry;
import com.ibm.commons.Platform;
import com.ibm.commons.runtime.util.UrlUtil;
import com.ibm.commons.util.PathUtil;
import com.ibm.commons.util.StringUtil;
import com.ibm.commons.util.io.json.JsonParser;
import com.ibm.jscript.JSContext;
import com.ibm.jscript.json.JsonJavaScriptFactory;
import com.ibm.jscript.std.ArrayObject;
import com.ibm.jscript.std.ObjectObject;
import com.ibm.jscript.types.FBSBoolean;
import com.ibm.jscript.types.FBSString;
import com.ibm.jscript.types.FBSUtility;
import com.ibm.jscript.types.FBSValue;
import com.ibm.sbt.playground.assets.AssetNode;
import com.ibm.sbt.playground.assets.CategoryNode;
import com.ibm.sbt.playground.assets.Node;
import com.ibm.sbt.playground.assets.apis.APIAssetNode;
import com.ibm.xsp.extlib.util.ExtLibUtil;
import com.ibm.xsp.util.JavaScriptUtil;
/**
* Classes that encapsulates the business logic for an API Description.
*
* @author priand
*/
public abstract class APIBean extends AssetBean {
public static final boolean TRACE = false;
public static final String FORM = "APIDescription";
private static JSContext jsContext = JavaScriptUtil.getJSContext();
private String id;
private String unid;
private ObjectObject cache;
public APIBean() {
}
protected String getAssetForm() {
return FORM;
}
public String getToolkitUrl() throws UnsupportedEncodingException, IOException {
Map<String,Object> sessionScope = ExtLibUtil.getSessionScope();
DataAccessBean dataAccess = DataAccessBean.get();
String envName = (String)sessionScope.get("environment");
if(StringUtil.isEmpty(envName)) {
envName = dataAccess.getPreferredEnvironment();
}
String url = "xsp/.sbtlibrary";
if(StringUtil.isNotEmpty(envName)) {
url += "?env="+encodeUrl(envName);
}
return url;
}
protected AssetNode createAssetNode(String notesUnid, CategoryNode parent, String name, String category, String assetId) {
return new APIAssetNode(parent,name,category,notesUnid,assetId);
}
public ObjectObject loadAPI(String id) throws NotesException {
return loadAPI(id,null);
}
public ObjectObject loadAPI(String id, String unid) throws NotesException {
// The method can be called when the page is created and the in the rendering phase
// we cache the value to avoid multiple DB access
// Not that this requires the bean to be set to the request scope, and not shared
if(cache!=null) {
if(StringUtil.equals(id, this.id) && StringUtil.equals(unid, this.unid)) {
return cache;
}
this.cache = null;
this.id = null;
this.unid = null;
}
ObjectObject o = new ObjectObject(jsContext);
try {
if(StringUtil.isNotEmpty(id)) {
Database database = ExtLibUtil.getCurrentDatabase();
Vector v = new Vector();
v.add(FORM);
v.add(id);
ViewEntry e = database.getView("AllSnippetsById").getEntryByKey(v);
if(e!=null) {
Document doc = e.getDocument();
Properties p = new Properties();
String props = doc.getItemValueString("Properties");
if(StringUtil.isNotEmpty(props)) {
p.load(new StringReader(props));
}
String endpoint = p.getProperty("endpoint");
if(StringUtil.isNotEmpty(endpoint)) {
o.put("endpoint", FBSString.get(endpoint));
}
String baseDocUrl = doc.getItemValueString("basedocurl");
if(StringUtil.isNotEmpty(baseDocUrl)) {
o.put("doc_url", FBSString.get(baseDocUrl));
}
String json = doc.getItemValueString("Json");
if(StringUtil.isNotEmpty(json)) {
FBSValue value = (FBSValue)JsonParser.fromJson(new JsonJavaScriptFactory(jsContext), json);
o.put("items", value);
}
fixAPIObject(o,baseDocUrl,unid);
}
}
} catch(Exception ex) {
Platform.getInstance().log(ex);
}
this.cache = o;
this.id = id;
this.unid = unid;
return o;
}
// Make sure the Json objects are what is expected
protected void fixAPIObject(ObjectObject o, String baseDocUrl, String unid) throws Exception {
ArrayObject items = (ArrayObject)o.get("items");
ArrayObject result = items;
if(StringUtil.isNotEmpty(unid)) {
result = new ArrayObject();
o.put("items", result);
}
int count = items.getArrayLength();
for(int i=0; i<count; i++) {
ObjectObject item = (ObjectObject)items.getArrayElement(i);
// Fix the unid if it doesn't exist
fixSnippetUnid(item);
// And then filter it, or just fix it
if(result!=items) {
String oid = item.get("unid").stringValue();
if(oid.equals(unid)) {
fixItem(item,baseDocUrl);
result.addArrayValue(item);
return;
}
} else {
fixItem(item,baseDocUrl);
}
}
}
protected void fixItem(ObjectObject item, String baseDocUrl) throws Exception {
// Load the URI parameters
fixUriParameters(item);
// Load the Query parameters
fixQueryParameters(item);
// Load the Header parameters
fixHeaders(item);
// Load the full documentation URL
fixDocUrl(item,baseDocUrl);
}
protected void fixSnippetUnid(ObjectObject o) throws Exception {
FBSValue unid = o.get("unid");
if(!unid.booleanValue()) {
unid = FBSString.get(Node.encodeUnid(o.get("name").stringValue()));
o.put("unid", unid);
}
}
protected void fixUriParameters(ObjectObject o) throws Exception {
FBSValue params = o.get("uriParameters");
if(!(params instanceof ArrayObject)) {
params = new ArrayObject();
((ObjectObject)o).put("uriParameters",params);
}
// Look if there are missing parameters in the URI
extract((ArrayObject)params,o,"uri");
}
protected void fixQueryParameters(ObjectObject o) throws Exception {
FBSValue params = o.get("queryParameters");
if(!(params instanceof ArrayObject)) {
params = new ArrayObject();
((ObjectObject)o).put("queryParameters",params);
}
}
protected void fixHeaders(ObjectObject o) throws Exception {
FBSValue params = o.get("headers");
if(!(params instanceof ArrayObject)) {
params = new ArrayObject();
((ObjectObject)o).put("headers",params);
}
}
protected void fixDocUrl(ObjectObject o, String baseDocUrl) throws Exception {
FBSValue params = o.get("doc_url");
if(!params.isNull()) {
String url = params.stringValue();
if(!UrlUtil.isAbsoluteUrl(url) && StringUtil.isNotEmpty(baseDocUrl)) {
o.put("doc_url", FBSString.get(PathUtil.concat(baseDocUrl, url, '/')));
}
}
}
private void extract(ArrayObject a, ObjectObject parent, String prop) throws Exception {
FBSValue v = parent.get(prop);
if(!v.isString()) {
return;
}
String value = v.stringValue();
int pos=0;
do {
int start = value.indexOf("{",pos);
if(start>=0) {
int end = value.indexOf("}",start);
if(end>start+1) {
FBSValue p = FBSUtility.wrap(value.substring(start+1,end).trim());
if(!contains(a,p.stringValue())) {
if(TRACE) {
System.out.println(StringUtil.format("Missing uri property {0}",p));
}
addParam(a, p, FBSString.emptyString, FBSUtility.wrap("string"), FBSString.emptyString);
}
pos = end + 1;
continue;
}
}
break;
} while(true);
}
private boolean contains(ArrayObject a, String name) throws Exception {
int count = a.getArrayLength();
for(int i=0; i<count; i++) {
FBSValue v = a.getArrayValue(i);
if(v.isJSObject()) {
FBSValue n = v.asObject().get("name");
if(n.isString() && n.stringValue().equals(name)) {
return true;
}
}
}
return false;
}
private ObjectObject addParam(ArrayObject a, FBSValue name, FBSValue value, FBSValue type, FBSValue description) throws Exception {
ObjectObject o = new ObjectObject();
o.put("name",name);
o.put("value",value);
o.put("type",type);
o.put("description",description);
a.addArrayValue(o);
return o;
}
public Object loadParameters(Object o) throws Exception {
ObjectObject item = (ObjectObject)o;
ArrayObject a = new ArrayObject(jsContext);
FBSValue a1 = (FBSValue)item.get("uriParameters");
if(a1.isArray()) {
for(int i=0; i<a1.getArrayLength(); i++) {
a.addArrayValue(a1.getArrayValue(i));
}
}
FBSValue a2 = (FBSValue)item.get("queryParameters");
if(a2.isArray()) {
for(int i=0; i<a2.getArrayLength(); i++) {
a.addArrayValue(a2.getArrayValue(i));
}
}
FBSValue a3 = (FBSValue)item.get("headers");
if(a3.isArray()) {
for(int i=0; i<a3.getArrayLength(); i++) {
a.addArrayValue(a3.getArrayValue(i));
}
}
// Add a pseudo query string parameter
boolean qstring = item.get("queryString").booleanValue();
if(qstring) {
ObjectObject qs = addParam(a, FBSString.get("query-string"), FBSString.emptyString, FBSUtility.wrap("string"), FBSUtility.wrap("Extra query string parameters, as they would appear in the URL (a=1&b=2...)"));
qs.put("optional", FBSBoolean.TRUE);
}
// Now, add the pseudo entries for the POST/PUT content
FBSValue v = item.get("http_method");
if(v.isString()) {
String m = v.stringValue();
if(m.equalsIgnoreCase("post")||m.equalsIgnoreCase("put")) {
ObjectObject ct = addParam(a, FBSString.get("post_content_type"), item.get("post_content_type"), FBSUtility.wrap("string"), FBSUtility.wrap("Content-Type header of the payload"));
ct.put("optional", FBSBoolean.TRUE);
ObjectObject bd = addParam(a, FBSString.get("post_content"), item.get("post_content"), FBSUtility.wrap("textarea"), FBSUtility.wrap("Text content sent to the service"));
bd.put("optional", FBSBoolean.TRUE);
}
}
return a;
}
public String[] split(Object value) {
if(value==null) {
return null;
}
String s = value.toString();
if(StringUtil.isEmpty(s)) {
return null;
}
ArrayList<String> l = new ArrayList<String>();
StringTokenizer tk = new StringTokenizer(s,",;\n");
while(tk.hasMoreElements()) {
String v = ((String)tk.nextElement()).trim();
if(StringUtil.isNotEmpty(v)) {
l.add(v);
}
}
return l.toArray(new String[l.size()]);
}
}