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