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    }