001 /* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019 package org.apache.wiki.rpc.json; 020 021 import java.lang.reflect.Method; 022 import java.security.Permission; 023 import java.util.HashMap; 024 import java.util.Iterator; 025 026 import javax.servlet.http.HttpServletRequest; 027 import javax.servlet.http.HttpSession; 028 029 import org.apache.log4j.Logger; 030 031 import org.apache.wiki.WikiContext; 032 import org.apache.wiki.WikiEngine; 033 import org.apache.wiki.WikiSession; 034 import org.apache.wiki.auth.WikiSecurityException; 035 import org.apache.wiki.auth.permissions.PagePermission; 036 import org.apache.wiki.rpc.RPCCallable; 037 import org.apache.wiki.rpc.RPCManager; 038 import org.apache.wiki.ui.TemplateManager; 039 import com.metaparadigm.jsonrpc.InvocationCallback; 040 import com.metaparadigm.jsonrpc.JSONRPCBridge; 041 042 /** 043 * Provides an easy-to-use interface for different modules to AJAX-enable 044 * themselves. This class is a static class, so it cannot be instantiated, 045 * but it easily available from anywhere (including JSP pages). 046 * <p/> 047 * Any object which wants to expose its methods through JSON calls, needs 048 * to implement the RPCCallable interface. JSONRPCManager will expose 049 * <i>all</i> methods, so be careful which you want to expose. 050 * <p/> 051 * Due to some limitations of the JSON-RPC library, we do not use the 052 * Global bridge object. 053 * 054 * @see org.apache.wiki.rpc.RPCCallable 055 * @since 2.5.4 056 */ 057 // FIXME: Must be mootool-ified. 058 public final class JSONRPCManager extends RPCManager { 059 private static final String JSONRPCBRIDGE = "JSONRPCBridge"; 060 private static HashMap<String, CallbackContainer> c_globalObjects = new HashMap<String, CallbackContainer>(); 061 062 /** 063 * Prevent instantiation 064 */ 065 private JSONRPCManager() { 066 super(); 067 } 068 069 /** 070 * Emits JavaScript to do a JSON RPC Call. You would use this method e.g. 071 * in your plugin generation code to embed an AJAX call to your object. 072 * 073 * @param context The Wiki Context 074 * @param c An RPCCallable object 075 * @param function Name of the method to call 076 * @param params Parameters to pass to the method 077 * @return generated JavasSript code snippet that calls the method 078 */ 079 public static String emitJSONCall(WikiContext context, RPCCallable c, String function, String params) { 080 081 StringBuffer sb = new StringBuffer(); 082 String method = getId(c) + "." + function; 083 sb.append("<span class='json-result' id='"+ method + "'></span>"); //placeholder to add results of jsonrpc 084 sb.append("<script>\r\n"); 085 086 //sb.append("var result = jsonrpc." + getId(c) + "." + function + "(" + params + ");\r\n"); 087 //sb.append("document.write(result);\r\n"); 088 089 sb.append("window.addEvent('domready', function(){ \r\n"); 090 sb.append(" Wiki.jsonrpc('" + method + "',[" + params + "],function(result){ \r\n"); 091 sb.append(" console.log(result);\r\n"); 092 sb.append(" $('" + method + "').innerHTML = result;\r\n"); 093 sb.append(" });\r\n"); 094 sb.append("});\r\n"); 095 096 sb.append("</script>\r\n"); 097 098 return sb.toString(); 099 } 100 101 /** 102 * Finds this user's personal RPC Bridge. If it does not exist, will 103 * create one and put it in the context. If there is no HTTP Request included, 104 * returns the global bridge. 105 * 106 * @param context WikiContext to find the bridge in 107 * @return A JSON RPC Bridge 108 */ 109 // FIXME: Is returning the global bridge a potential security threat? 110 private static JSONRPCBridge getBridge(WikiContext context) { 111 JSONRPCBridge bridge = null; 112 HttpServletRequest req = context.getHttpRequest(); 113 114 if (req != null) { 115 HttpSession hs = req.getSession(); 116 117 if (hs != null) { 118 bridge = (JSONRPCBridge) hs.getAttribute(JSONRPCBRIDGE); 119 120 if (bridge == null) { 121 bridge = new JSONRPCBridge(); 122 123 hs.setAttribute(JSONRPCBRIDGE, bridge); 124 } 125 } 126 } 127 128 if (bridge == null) { 129 bridge = JSONRPCBridge.getGlobalBridge(); 130 } 131 bridge.setDebug(false); 132 133 return bridge; 134 } 135 136 /** 137 * Registers a callable to JSON global bridge and requests JSON libraries to be added 138 * to the page. 139 * 140 * @param context The WikiContext. 141 * @param c The RPCCallable to register 142 * @return the ID of the registered callable object 143 */ 144 public static String registerJSONObject(WikiContext context, RPCCallable c) { 145 146 String id = getId(c); 147 148 getBridge(context).registerObject(id, c); 149 requestJSON(context); 150 151 return id; 152 } 153 154 /** 155 * Requests the JSON Javascript and object to be generated in the HTML. 156 * 157 * @param context The WikiContext. 158 */ 159 public static void requestJSON(WikiContext context) { 160 161 /* Deprecated. 162 All json stuff is in jspwiki-common.js; not need to inject jsonrpc.js 163 TemplateManager.addResourceRequest(context, 164 TemplateManager.RESOURCE_SCRIPT, 165 context.getURL(WikiContext.NONE, "scripts/json-rpc/jsonrpc.js")); 166 167 String jsonurl = context.getURL(WikiContext.NONE, "JSON-RPC"); 168 TemplateManager.addResourceRequest(context, 169 TemplateManager.RESOURCE_JSFUNCTION, 170 "jsonrpc = new JSONRpcClient(\"" + jsonurl + "\");"); 171 */ 172 173 getBridge(context).registerCallback(new WikiJSONAccessor(), HttpServletRequest.class); 174 } 175 176 /** 177 * Provides access control to the JSON calls. Rather private. 178 * Unfortunately we have to check the permission every single time, because 179 * the user can log in and we would need to reset the permissions at that time. 180 * Note that this is an obvious optimization piece if this becomes 181 * a bottleneck. 182 */ 183 static class WikiJSONAccessor implements InvocationCallback { 184 private static final long serialVersionUID = 1L; 185 private static final Logger log = Logger.getLogger(WikiJSONAccessor.class); 186 187 /** 188 * Create an accessor. 189 */ 190 public WikiJSONAccessor() { 191 } 192 193 /** 194 * Does not do anything. 195 * <p/> 196 * {@inheritDoc} 197 */ 198 public void postInvoke(Object context, Object instance, Method method, Object result) throws Exception { 199 } 200 201 /** 202 * Checks access against the permission given. 203 * <p/> 204 * {@inheritDoc} 205 */ 206 public void preInvoke(Object context, Object instance, Method method, Object[] arguments) throws Exception { 207 if (context instanceof HttpServletRequest) { 208 boolean canDo = false; 209 HttpServletRequest req = (HttpServletRequest) context; 210 211 WikiEngine e = WikiEngine.getInstance(req.getSession().getServletContext(), null); 212 213 for (Iterator i = c_globalObjects.values().iterator(); i.hasNext(); ) { 214 CallbackContainer cc = (CallbackContainer) i.next(); 215 216 if (cc.m_object == instance) { 217 canDo = e.getAuthorizationManager().checkPermission(WikiSession.getWikiSession(e, req), 218 cc.m_permission); 219 220 break; 221 } 222 } 223 224 if (canDo) { 225 return; 226 } 227 } 228 229 log.debug("Failed JSON permission check: " + instance); 230 throw new WikiSecurityException("No permission to access this AJAX method!"); 231 } 232 233 } 234 235 /** 236 * Registers a global object (i.e. something which can be called by any 237 * JSP page). Typical examples is e.g. "search". By default, the RPCCallable 238 * shall need a "view" permission to access. 239 * 240 * @param id The name under which this shall be registered (e.g. "search") 241 * @param object The RPCCallable which shall be associated to this id. 242 */ 243 public static void registerGlobalObject(String id, RPCCallable object) { 244 registerGlobalObject(id, object, PagePermission.VIEW); 245 } 246 247 /** 248 * Registers a global object (i.e. something which can be called by any 249 * JSP page) with a specific permission. 250 * 251 * @param id The name under which this shall be registered (e.g. "search") 252 * @param object The RPCCallable which shall be associated to this id. 253 * @param perm The permission which is required to access this object. 254 */ 255 public static void registerGlobalObject(String id, RPCCallable object, Permission perm) { 256 CallbackContainer cc = new CallbackContainer(); 257 cc.m_permission = perm; 258 cc.m_id = id; 259 cc.m_object = object; 260 261 c_globalObjects.put(id, cc); 262 } 263 264 /** 265 * Is called whenever a session is created. This method creates a new JSONRPCBridge 266 * and adds it to the user session. This is done because the global JSONRPCBridge 267 * InvocationCallbacks are not called; only session locals. This may be a bug 268 * in JSON-RPC, or it may be a design feature... 269 * <p/> 270 * The JSONRPCBridge object will go away once the session expires. 271 * 272 * @param session The HttpSession which was created. 273 */ 274 public static void sessionCreated(HttpSession session) { 275 JSONRPCBridge bridge = (JSONRPCBridge) session.getAttribute(JSONRPCBRIDGE); 276 277 if (bridge == null) { 278 bridge = new JSONRPCBridge(); 279 280 session.setAttribute(JSONRPCBRIDGE, bridge); 281 } 282 283 WikiJSONAccessor acc = new WikiJSONAccessor(); 284 285 bridge.registerCallback(acc, HttpServletRequest.class); 286 287 for (Iterator i = c_globalObjects.values().iterator(); i.hasNext(); ) { 288 CallbackContainer cc = (CallbackContainer) i.next(); 289 290 bridge.registerObject(cc.m_id, cc.m_object); 291 } 292 293 } 294 295 /** 296 * Just stores the registered global method. 297 */ 298 private static class CallbackContainer { 299 String m_id; 300 RPCCallable m_object; 301 Permission m_permission; 302 } 303 }