/*
* Copyright 2008-2011 the original author or authors.
*
* 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.
*/
/*
* AZ: this version merges with the model the request's body, if it is a json,
* and initializes every object of type Location among the scope's java objects.
*/
package com.nominanuda.springmvc;
import static com.nominanuda.rhino.StruScriptableConvertor.DSS_CONVERTOR;
import static com.nominanuda.web.http.HttpCoreHelper.HTTP;
import static com.nominanuda.zen.obj.JsonPath.JPATH;
import static org.mozilla.javascript.RhinoHelper.RHINO;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import org.apache.http.HttpEntity;
import org.apache.http.HttpRequest;
import org.apache.http.NameValuePair;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.RhinoEmbedding;
import org.mozilla.javascript.ScopeFactory;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.SpringScopeFactory;
import com.nominanuda.hyperapi.AnnotatedType;
import com.nominanuda.hyperapi.StruJsonDecoder;
import com.nominanuda.hyperapi.EntityDecoder;
import com.nominanuda.jsweb.host.JsHttpRequest;
import com.nominanuda.jsweb.host.Location;
import com.nominanuda.urispec.URISpec;
import com.nominanuda.web.mvc.CommandRequestHandler;
import com.nominanuda.web.mvc.ObjURISpec;
import com.nominanuda.zen.common.Tuple2;
import com.nominanuda.zen.obj.Obj;
import com.nominanuda.zen.obj.Stru;
public class RhinoHandler implements CommandRequestHandler {
private final static String ENTITY_ARRAY_CMD_KEY = "_entity";
private final static String REQUEST_EXTRA_PATTERNID = "patternId";
protected final EntityDecoder jsonDecoder = new StruJsonDecoder();
protected Sitemap sitemap;
protected String patternId;
protected URISpec<Obj> uriSpec;
protected RhinoEmbedding rhinoEmbedding;
protected ScriptableObject cachedScope;
protected ScopeFactory scopeFactory;
protected boolean allowJavaPackageAccess = true; // TODO security turn to false default policy
protected boolean mergeGetAndPostFormParams = true;
protected boolean mergeEntityDataObject = true;
protected String function = "handle";
public void init() {
// configure locations
for (Object o : scopeFactory.getJavaObjects().values()) {
if (o instanceof Location) {
((Location)o).setSitemap(sitemap);
}
}
// if the resource location doesn't depend from request's data, execute the script once (for errors spotting, caching,...)
Context cx = rhinoEmbedding.enterContext();
try {
if (uriSpec.toString().equals(calcScriptUri(Obj.make(), null))) {
evaluateScript(cx, buildScope(cx), uriSpec.toString());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
Context.exit();
}
}
@Override
public Stru handle(Stru cmd, HttpRequest request) throws Exception {
Context cx = rhinoEmbedding.enterContext();
try {
HttpEntity entity = HTTP.getEntity(request);
List<NameValuePair> pairs = Collections.emptyList();
if (mergeGetAndPostFormParams) {
Obj cmdFromReq = HTTP.getQueryParams(request).asObj();
if (entity != null) {
pairs = HTTP.parseEntityWithDefaultUtf8(entity);
JPATH.copyPush(HTTP.toStru(pairs).asObj(), cmdFromReq);
}
// TODO
// here request params hide uriparams
JPATH.copyOverwite(cmd.asObj(), cmdFromReq);
cmd = cmdFromReq;
}
if (mergeEntityDataObject && entity != null && pairs.isEmpty()) { // if pairs isn't empty -> entity was already consumed
try {
// DataStruct because it could be an array
Stru structFromEntity = (Stru) jsonDecoder.decode(new AnnotatedType(Stru.class, new Annotation[] {}), entity);
Obj cmdFromEntity = (structFromEntity.isArr() // nees to be a Obj before merging with cmd
? Obj.make(ENTITY_ARRAY_CMD_KEY, structFromEntity)
: structFromEntity.asObj());
JPATH.copyOverwite(cmd.asObj(), cmdFromEntity);
cmd = cmdFromEntity;
} catch (Exception e) {
// better way than try/catch?
}
}
Scriptable controllerScope = buildScope(cx);
evaluateScript(cx, controllerScope, calcScriptUri(cmd, request));
Object res = executeFunction(cx, controllerScope, function, cmd, request);
return DSS_CONVERTOR.fromScriptable((Scriptable)res);
} finally {
Context.exit();
}
}
protected String calcScriptUri(Stru cmd, HttpRequest request) throws IOException {
return uriSpec.template(cmd.asObj());
}
protected void evaluateScript(Context cx, Scriptable controllerScope, String scriptUri) throws IOException {
Tuple2<String,Reader> script = getSource(scriptUri);
RHINO.evaluateReader(cx, script.get1(), script.get0(), controllerScope);
}
protected Tuple2<String, Reader> getSource(String uri) throws IOException {
String jsLocation = uri.replace("classpath:", ""); // allows JSDT remote rhino debugging
return new Tuple2<String, Reader>(jsLocation, new InputStreamReader(new URL(uri).openStream(), "UTF-8"));
}
protected Object executeFunction(Context cx, Scriptable controllerScope, String function, Stru cmd, HttpRequest request) {
Scriptable jsCmd = DSS_CONVERTOR.toScriptable(cx, cmd, controllerScope);
JsHttpRequest jsReq = (JsHttpRequest) cx.newObject(controllerScope, "HttpRequest", new Object[] {
request,
Obj.make(
REQUEST_EXTRA_PATTERNID, patternId
)
});
return RHINO.callFunctionInScope(cx, controllerScope, function, new Object[] { jsCmd, jsReq });
}
private Scriptable buildScope(Context cx) throws Exception {
if (scopeFactory == null) {
if (cachedScope == null) {
cachedScope = RHINO.createTopScope(cx, allowJavaPackageAccess);
}
return RHINO.protocloneScriptable(cx, cachedScope);
} else {
return scopeFactory.createInContext(cx);
}
}
/* setters */
public void setMergeGetAndPostFormParams(boolean mergeGetAndPostFormParams) {
this.mergeGetAndPostFormParams = mergeGetAndPostFormParams;
}
public void setMergeEntityDataObject(boolean mergeEntityDataObject) {
this.mergeEntityDataObject = mergeEntityDataObject;
}
public void setSpringScopeFactory(SpringScopeFactory scopeFactory) {
allowJavaPackageAccess = scopeFactory.isAllowJavaPackageAccess();
rhinoEmbedding = scopeFactory.getEmbedding();
this.scopeFactory = scopeFactory;
}
public void setSitemap(Sitemap sitemap) {
this.sitemap = sitemap;
}
public void setFunction(String function) {
this.function = function;
}
public void setPatternId(String patternId) {
this.patternId = patternId;
}
public void setUriSpec(String uriSpecTemplate) {
this.uriSpec = new ObjURISpec(uriSpecTemplate);
}
}