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 }