001 /* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 * 014 * See the NOTICE file distributed with this work for additional 015 * information regarding copyright ownership. 016 */ 017 018 package com.osbcp.apbs; 019 020 import java.io.IOException; 021 import java.io.Serializable; 022 import java.lang.reflect.Constructor; 023 import java.util.Map; 024 025 import javax.servlet.ServletException; 026 import javax.servlet.http.HttpServlet; 027 import javax.servlet.http.HttpServletRequest; 028 import javax.servlet.http.HttpServletResponse; 029 030 import com.osbcp.apbs.events.AjaxPostBackEvent; 031 import com.osbcp.jjsw.JavaScript; 032 033 /** 034 * A specific HttpServlet for AjaxPostBack. 035 * 036 * @author <a href="mailto:christoffer@christoffer.me">Christoffer Pettersson</a> 037 */ 038 039 public abstract class AjaxPostBackServlet extends HttpServlet { 040 041 private static final long serialVersionUID = 1L; 042 043 @Override 044 public final void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { 045 046 // Get the data from the query string 047 String eventName = request.getParameter("event"); 048 String[] arguments = request.getParameterValues("argument"); 049 050 // Populate the meta data map 051 Map<Serializable, Object> metaData = getMetaData(request, response); 052 053 // Get the response script 054 JavaScript responseScript = getJavaScriptResponse(eventName, arguments, metaData); 055 056 // Print out the JavaScript 057 printJavaScriptResponse(response, responseScript); 058 059 } 060 061 /** 062 * Logic that invokes the given event with the specified arguments and meta data. 063 * 064 * @param eventName The class name of the event to be triggered. 065 * @param arguments The constructor arguments to the event. 066 * @param metaData Any meta data that should be included in the event. 067 * @return A JavaScript response that should be performed by the browser. 068 */ 069 070 @SuppressWarnings("unchecked") 071 private JavaScript getJavaScriptResponse(final String eventName, final String[] arguments, final Map<Serializable, Object> metaData) { 072 073 // Create a new response container 074 JavaScript response = new JavaScript(); 075 076 // Save some variables for logging 077 long startTime = System.currentTimeMillis(); 078 079 try { 080 081 // Get the event class 082 final Class<? extends AjaxPostBackEvent> eventClass = (Class<? extends AjaxPostBackEvent>) Class.forName(eventName); 083 084 // Get a new instance of the event 085 Object event = invokeEvent(eventClass, arguments); 086 087 // Cast it as an AjaxPostBackEvent 088 AjaxPostBackEvent apb = (AjaxPostBackEvent) event; 089 090 // Set any meta data on the event 091 apb.setMetaData(metaData); 092 093 // Get the response from the event 094 response.append(apb.getJavaScriptResponse()); 095 096 } catch (final Exception exception) { 097 098 // Calculate the duration 099 long duration = System.currentTimeMillis() - startTime; 100 101 // Bubble the error 102 onError(eventName, arguments, duration, response); 103 104 // Get the response if any exception occurs 105 return getExceptionJavaScriptResponse(eventName, arguments, duration, exception); 106 107 } 108 109 // Calculate the duration 110 long duration = System.currentTimeMillis() - startTime; 111 112 // Bubble the success 113 onSuccess(eventName, arguments, duration, response); 114 115 // Return the response 116 return response; 117 118 } 119 120 /** 121 * Invokes a specific event with specific arguments. 122 * 123 * @param eventClass The class of the event to be triggered. 124 * @param arguments The constructor arguments to the event. 125 * @return An instance of the event. 126 * @throws Exception If any error occurs. 127 */ 128 129 private Object invokeEvent(final Class<? extends AjaxPostBackEvent> eventClass, final String[] arguments) throws Exception { 130 131 if (arguments != null) { 132 133 Constructor<?>[] constructors = eventClass.getConstructors(); 134 135 for_each_constructor: for (Constructor<?> constructor : constructors) { 136 137 Class<?>[] constructorTypes = constructor.getParameterTypes(); 138 139 // If they don't have the same number of slots, continue to the next constructor 140 if (constructorTypes.length != arguments.length) { 141 continue; 142 } 143 144 // Create an array for the arguments we are going to type cast 145 Object[] castedArguments = new Object[constructorTypes.length]; 146 147 for (int i = 0; i < arguments.length; i++) { 148 149 try { 150 151 // Try and cast the argument to what the constructor expects 152 castedArguments[i] = cast(arguments[i], constructorTypes[i]); 153 154 } catch (ClassCastException exception) { 155 156 // If there is a class cast exception, this is the incorrect constructor 157 continue for_each_constructor; 158 159 } 160 161 } 162 163 // Invoke the event using the newly casted arguments 164 return constructor.newInstance(castedArguments); 165 166 } 167 168 throw new Exception("Could not find any suitable constructor for the class '" + eventClass.getSimpleName() + "' for the given arguments " + arguments + "."); 169 170 } else { 171 172 // Create an instance of the event with the default empty constructor 173 return eventClass.newInstance(); 174 175 } 176 177 } 178 179 /** 180 * Casts a specific argument to a specific legal type. 181 * 182 * @param argument The argument that should be casted. 183 * @param castTo The type the argument should be casted to. 184 * @return The argument casted to a legal type. 185 * @throws ClassCastException If any error occurs. 186 */ 187 188 private Object cast(final String argument, final Class<?> castTo) throws ClassCastException { 189 190 try { 191 192 if (castTo == String.class) { 193 return argument; 194 } else if (castTo == Byte.class || castTo == Byte.TYPE) { 195 return Byte.parseByte(argument); 196 } else if (castTo == Short.class || castTo == Short.TYPE) { 197 return Short.parseShort(argument); 198 } else if (castTo == Integer.class || castTo == Integer.TYPE) { 199 return Integer.parseInt(argument); 200 } else if (castTo == Long.class || castTo == Long.TYPE) { 201 return Long.parseLong(argument); 202 } else if (castTo == Float.class || castTo == Float.TYPE) { 203 return Float.parseFloat(argument); 204 } else if (castTo == Double.class || castTo == Double.TYPE) { 205 return Double.parseDouble(argument); 206 } else if (castTo == Boolean.class || castTo == Boolean.TYPE) { 207 return Boolean.valueOf(argument); 208 } else if (castTo == Character.class || castTo == Character.TYPE) { 209 return argument.charAt(0); 210 } 211 212 } catch (Exception e) { 213 // Fall through 214 } 215 216 throw new ClassCastException("The argument '" + argument + "' could not be casted to anything legal."); 217 218 } 219 220 /** 221 * Writes a specific JavaScript script to the HTTP response stream. 222 * 223 * @param response The HTTP response. 224 * @param responseScript The JavaScript script that should be written. 225 * @throws IOException If any error occurs. 226 */ 227 228 private void printJavaScriptResponse(final HttpServletResponse response, final JavaScript responseScript) throws IOException { 229 230 response.setCharacterEncoding("UTF-8"); 231 response.setContentType("text/javascript"); 232 response.getWriter().println(responseScript); 233 234 } 235 236 /** 237 * Used to specific specific meta data that should be included in AjaxPostBackEvents. 238 * 239 * @param request The HTTP request. 240 * @param response The HTTP response. 241 * @return A map of meta data that should be included in AjaxPostBackEvents. 242 */ 243 244 protected abstract Map<Serializable, Object> getMetaData(final HttpServletRequest request, final HttpServletResponse response); 245 246 /** 247 * Returns the JavaScript script that should be sent to the browser if any exception error occurs. 248 * 249 * @param eventName The class name of the event. 250 * @param arguments The arguments sent to the event. 251 * @param duration The duration in milliseconds of the event logic. 252 * @param exception The exception error that occurred. 253 * @return The JavaScript script that should be sent to the browser if any exception error occurs. 254 */ 255 256 protected abstract JavaScript getExceptionJavaScriptResponse(final String eventName, final String[] arguments, final long duration, final Exception exception); 257 258 /** 259 * Triggers when an event is successfully triggered. 260 * 261 * @param eventName The class name of the event. 262 * @param arguments The arguments sent to the event. 263 * @param duration The duration in milliseconds of the event logic. 264 * @param response The JavaScript response the event returned. 265 */ 266 267 protected abstract void onSuccess(final String eventName, final String[] arguments, final long duration, final JavaScript response); 268 269 /** 270 * Triggers when an event failed. 271 * 272 * @param eventName The class name of the event. 273 * @param arguments The arguments sent to the event. 274 * @param duration The duration in milliseconds of the event logic. 275 * @param response The JavaScript response the event returned. 276 */ 277 278 protected abstract void onError(final String eventName, final String[] arguments, final long duration, final JavaScript response); 279 280 }